diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 43c24f218d..f52cdf3e49 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -311,7 +311,7 @@ MetavoxelPersister::MetavoxelPersister(MetavoxelServer* server) : const char* SAVE_FILE = "/resources/metavoxels.dat"; const int FILE_MAGIC = 0xDADAFACE; -const int FILE_VERSION = 2; +const int FILE_VERSION = 3; void MetavoxelPersister::load() { QString path = QCoreApplication::applicationDirPath() + SAVE_FILE; diff --git a/interface/resources/shaders/metavoxel_voxel_splat.vert b/interface/resources/shaders/metavoxel_voxel_splat.vert index 31ddbef395..1cd1bfb6ba 100644 --- a/interface/resources/shaders/metavoxel_voxel_splat.vert +++ b/interface/resources/shaders/metavoxel_voxel_splat.vert @@ -11,6 +11,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// the splat texture offset +uniform vec3 splatTextureOffset; + // the splat textures scales on the S axis uniform vec4 splatTextureScalesS; @@ -43,7 +46,7 @@ void main(void) { normal = gl_Normal; // pass along the scaled/offset texture coordinates - vec4 textureSpacePosition = gl_Vertex.xyyz; + vec4 textureSpacePosition = (gl_Vertex.xyz + splatTextureOffset).xyyz; gl_TexCoord[0] = textureSpacePosition * vec4(splatTextureScalesS[0], splatTextureScalesT[0], splatTextureScalesS[0], splatTextureScalesT[0]); gl_TexCoord[1] = textureSpacePosition * vec4(splatTextureScalesS[1], splatTextureScalesT[1], diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 38c7ab843f..57134bb84b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -419,9 +419,8 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); QMenu* metavoxelOptionsMenu = developerMenu->addMenu("Metavoxels"); - addCheckableActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::DisplayHermiteData, 0, false, - Application::getInstance()->getMetavoxels(), SLOT(refreshVoxelData())); - addCheckableActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::RenderSpanners, 0, true); + addCheckableActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::DisplayHermiteData, 0, false); + addCheckableActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::RenderHeightfields, 0, true); addCheckableActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::RenderDualContourSurfaces, 0, true); addActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::NetworkSimulator, 0, this, SLOT(showMetavoxelNetworkSimulator())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b2854f8131..b1ba1b11d4 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -424,9 +424,9 @@ namespace MenuOption { const QString RenderDualContourSurfaces = "Render Dual Contour Surfaces"; const QString RenderFocusIndicator = "Show Eye Focus"; const QString RenderHeadCollisionShapes = "Show Head Collision Shapes"; + const QString RenderHeightfields = "Render Heightfields"; const QString RenderLookAtVectors = "Show Look-at Vectors"; const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes"; - const QString RenderSpanners = "Render Spanners"; const QString RenderTargetFramerate = "Framerate"; const QString RenderTargetFramerateUnlimited = "Unlimited"; const QString RenderTargetFramerate60 = "60"; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index e3ce097285..a566b18b7e 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + // include this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -34,6 +36,8 @@ #include "Application.h" #include "MetavoxelSystem.h" +using namespace std; + REGISTER_META_OBJECT(DefaultMetavoxelRendererImplementation) REGISTER_META_OBJECT(SphereRenderer) REGISTER_META_OBJECT(CuboidRenderer) @@ -59,11 +63,6 @@ MetavoxelSystem::~MetavoxelSystem() { void MetavoxelSystem::init() { MetavoxelClientManager::init(); - _voxelBufferAttribute = AttributeRegistry::getInstance()->registerAttribute( - new BufferDataAttribute("voxelBuffer")); - _voxelBufferAttribute->setLODThresholdMultiplier( - AttributeRegistry::getInstance()->getVoxelColorAttribute()->getLODThresholdMultiplier()); - _baseHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, PathUtils::resourcesPath() + "shaders/metavoxel_heightfield_base.vert"); _baseHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, PathUtils::resourcesPath() + @@ -202,7 +201,7 @@ void MetavoxelSystem::render() { RenderVisitor renderVisitor(getLOD()); guideToAugmented(renderVisitor, true); - if (!_heightfieldBaseBatches.isEmpty()) { + if (!_heightfieldBaseBatches.isEmpty() && Menu::getInstance()->isOptionChecked(MenuOption::RenderHeightfields)) { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); @@ -224,8 +223,8 @@ void MetavoxelSystem::render() { glRotatef(glm::degrees(glm::angle(batch.rotation)), axis.x, axis.y, axis.z); glScalef(batch.scale.x, batch.scale.y, batch.scale.z); - batch.vertexBuffer->bind(); - batch.indexBuffer->bind(); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBufferID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.indexBufferID); HeightfieldPoint* point = 0; glVertexPointer(3, GL_FLOAT, sizeof(HeightfieldPoint), &point->vertex); @@ -246,8 +245,8 @@ void MetavoxelSystem::render() { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); - batch.vertexBuffer->release(); - batch.indexBuffer->release(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glPopMatrix(); } @@ -274,8 +273,8 @@ void MetavoxelSystem::render() { glRotatef(glm::degrees(glm::angle(batch.rotation)), axis.x, axis.y, axis.z); glScalef(batch.scale.x, batch.scale.y, batch.scale.z); - batch.vertexBuffer->bind(); - batch.indexBuffer->bind(); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBufferID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.indexBufferID); HeightfieldPoint* point = 0; glVertexPointer(3, GL_FLOAT, sizeof(HeightfieldPoint), &point->vertex); @@ -325,8 +324,8 @@ void MetavoxelSystem::render() { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); - batch.vertexBuffer->release(); - batch.indexBuffer->release(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glPopMatrix(); } @@ -336,19 +335,17 @@ void MetavoxelSystem::render() { glDisable(GL_POLYGON_OFFSET_FILL); glDepthMask(true); glDepthFunc(GL_LESS); - - _heightfieldSplatBatches.clear(); } glDisable(GL_CULL_FACE); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); - - _heightfieldBaseBatches.clear(); } + _heightfieldBaseBatches.clear(); + _heightfieldSplatBatches.clear(); - if (!_voxelBaseBatches.isEmpty()) { + if (!_voxelBaseBatches.isEmpty() && Menu::getInstance()->isOptionChecked(MenuOption::RenderDualContourSurfaces)) { DependencyManager::get()->setPrimaryDrawBuffers(true, true); glEnableClientState(GL_VERTEX_ARRAY); @@ -364,9 +361,15 @@ void MetavoxelSystem::render() { _baseVoxelProgram.bind(); - foreach (const VoxelBatch& batch, _voxelBaseBatches) { - batch.vertexBuffer->bind(); - batch.indexBuffer->bind(); + foreach (const MetavoxelBatch& batch, _voxelBaseBatches) { + glPushMatrix(); + glTranslatef(batch.translation.x, batch.translation.y, batch.translation.z); + glm::vec3 axis = glm::axis(batch.rotation); + glRotatef(glm::degrees(glm::angle(batch.rotation)), axis.x, axis.y, axis.z); + glScalef(batch.scale.x, batch.scale.y, batch.scale.z); + + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBufferID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.indexBufferID); VoxelPoint* point = 0; glVertexPointer(3, GL_FLOAT, sizeof(VoxelPoint), &point->vertex); @@ -375,8 +378,10 @@ void MetavoxelSystem::render() { glDrawRangeElements(GL_QUADS, 0, batch.vertexCount - 1, batch.indexCount, GL_UNSIGNED_INT, 0); - batch.vertexBuffer->release(); - batch.indexBuffer->release(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glPopMatrix(); } _baseVoxelProgram.release(); @@ -398,8 +403,14 @@ void MetavoxelSystem::render() { _splatVoxelProgram.enableAttributeArray(_splatVoxelLocations.materialWeights); foreach (const VoxelSplatBatch& batch, _voxelSplatBatches) { - batch.vertexBuffer->bind(); - batch.indexBuffer->bind(); + glPushMatrix(); + glTranslatef(batch.translation.x, batch.translation.y, batch.translation.z); + glm::vec3 axis = glm::axis(batch.rotation); + glRotatef(glm::degrees(glm::angle(batch.rotation)), axis.x, axis.y, axis.z); + glScalef(batch.scale.x, batch.scale.y, batch.scale.z); + + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBufferID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.indexBufferID); VoxelPoint* point = 0; glVertexPointer(3, GL_FLOAT, sizeof(VoxelPoint), &point->vertex); @@ -410,8 +421,9 @@ void MetavoxelSystem::render() { GL_UNSIGNED_BYTE, (qint64)&point->materials, SPLAT_COUNT, sizeof(VoxelPoint)); _splatVoxelProgram.setAttributeBuffer(_splatVoxelLocations.materialWeights, GL_UNSIGNED_BYTE, (qint64)&point->materialWeights, SPLAT_COUNT, sizeof(VoxelPoint)); - + const float QUARTER_STEP = 0.25f * EIGHT_BIT_MAXIMUM_RECIPROCAL; + _splatVoxelProgram.setUniform(_splatVoxelLocations.splatTextureOffset, batch.splatTextureOffset); _splatVoxelProgram.setUniform(_splatVoxelLocations.splatTextureScalesS, batch.splatTextureScalesS); _splatVoxelProgram.setUniform(_splatVoxelLocations.splatTextureScalesT, batch.splatTextureScalesT); _splatVoxelProgram.setUniformValue( @@ -441,8 +453,10 @@ void MetavoxelSystem::render() { glActiveTexture(GL_TEXTURE0); - batch.vertexBuffer->release(); - batch.indexBuffer->release(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glPopMatrix(); } glDisable(GL_POLYGON_OFFSET_FILL); @@ -451,17 +465,15 @@ void MetavoxelSystem::render() { _splatVoxelProgram.disableAttributeArray(_splatVoxelLocations.materials); _splatVoxelProgram.disableAttributeArray(_splatVoxelLocations.materialWeights); - - _voxelSplatBatches.clear(); } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_CULL_FACE); - - _voxelBaseBatches.clear(); } + _voxelBaseBatches.clear(); + _voxelSplatBatches.clear(); if (!_hermiteBatches.isEmpty() && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHermiteData)) { DependencyManager::get()->setPrimaryDrawBuffers(true, true); @@ -474,13 +486,21 @@ void MetavoxelSystem::render() { DependencyManager::get()->bindSimpleProgram(); foreach (const HermiteBatch& batch, _hermiteBatches) { - batch.vertexBuffer->bind(); + glPushMatrix(); + glTranslatef(batch.translation.x, batch.translation.y, batch.translation.z); + glm::vec3 axis = glm::axis(batch.rotation); + glRotatef(glm::degrees(glm::angle(batch.rotation)), axis.x, axis.y, axis.z); + glScalef(batch.scale.x, batch.scale.y, batch.scale.z); + + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBufferID); glVertexPointer(3, GL_FLOAT, 0, 0); glDrawArrays(GL_LINES, 0, batch.vertexCount); - batch.vertexBuffer->release(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glPopMatrix(); } DependencyManager::get()->releaseSimpleProgram(); @@ -495,88 +515,29 @@ void MetavoxelSystem::render() { emit rendering(); } -void MetavoxelSystem::refreshVoxelData() { - DependencyManager::get()->eachNode([](const SharedNodePointer& node){ - if (node->getType() == NodeType::MetavoxelServer) { - QMutexLocker locker(&node->getMutex()); - MetavoxelSystemClient* client = static_cast(node->getLinkedData()); - if (client) { - QMetaObject::invokeMethod(client, "refreshVoxelData"); - } - } - }); -} - -class RayVoxelIntersectionVisitor : public RayIntersectionVisitor { -public: - - float intersectionDistance; - - RayVoxelIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, const MetavoxelLOD& lod); - - virtual int visit(MetavoxelInfo& info, float distance); -}; - -RayVoxelIntersectionVisitor::RayVoxelIntersectionVisitor(const glm::vec3& origin, - const glm::vec3& direction, const MetavoxelLOD& lod) : - RayIntersectionVisitor(origin, direction, QVector() << - Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(), QVector(), lod), - intersectionDistance(FLT_MAX) { -} - -int RayVoxelIntersectionVisitor::visit(MetavoxelInfo& info, float distance) { - if (!info.isLeaf) { - return _order; - } - const VoxelBuffer* buffer = static_cast( - info.inputValues.at(0).getInlineValue().data()); - if (!buffer) { - return STOP_RECURSION; - } - glm::vec3 entry = ((_origin + distance * _direction) - info.minimum) / info.size; - if (buffer->findFirstRayIntersection(entry, _origin, _direction, intersectionDistance)) { - return SHORT_CIRCUIT; - } - return STOP_RECURSION; -} - -bool MetavoxelSystem::findFirstRayVoxelIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) { - RayVoxelIntersectionVisitor visitor(origin, direction, getLOD()); - guideToAugmented(visitor); - if (visitor.intersectionDistance == FLT_MAX) { - return false; - } - distance = visitor.intersectionDistance; - return true; -} - void MetavoxelSystem::paintHeightfieldColor(const glm::vec3& position, float radius, const QColor& color) { - MetavoxelEditMessage edit = { QVariant::fromValue(PaintHeightfieldMaterialEdit(position, radius, SharedObjectPointer(), color)) }; - applyEdit(edit, true); + Sphere* sphere = new Sphere(); + sphere->setTranslation(position); + sphere->setScale(radius); + setHeightfieldColor(SharedObjectPointer(sphere), color, true); } void MetavoxelSystem::paintHeightfieldMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material) { - MetavoxelEditMessage edit = { QVariant::fromValue(PaintHeightfieldMaterialEdit(position, radius, material)) }; - applyMaterialEdit(edit, true); + Sphere* sphere = new Sphere(); + sphere->setTranslation(position); + sphere->setScale(radius); + setHeightfieldMaterial(SharedObjectPointer(sphere), material, true); } -void MetavoxelSystem::paintVoxelColor(const glm::vec3& position, float radius, const QColor& color) { - MetavoxelEditMessage edit = { QVariant::fromValue(PaintVoxelMaterialEdit(position, radius, SharedObjectPointer(), color)) }; +void MetavoxelSystem::setHeightfieldColor(const SharedObjectPointer& spanner, const QColor& color, bool paint) { + MetavoxelEditMessage edit = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(spanner, + SharedObjectPointer(), color, paint)) }; applyEdit(edit, true); } -void MetavoxelSystem::paintVoxelMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material) { - MetavoxelEditMessage edit = { QVariant::fromValue(PaintVoxelMaterialEdit(position, radius, material)) }; - applyMaterialEdit(edit, true); -} - -void MetavoxelSystem::setVoxelColor(const SharedObjectPointer& spanner, const QColor& color) { - MetavoxelEditMessage edit = { QVariant::fromValue(VoxelMaterialSpannerEdit(spanner, SharedObjectPointer(), color)) }; - applyEdit(edit, true); -} - -void MetavoxelSystem::setVoxelMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material) { - MetavoxelEditMessage edit = { QVariant::fromValue(VoxelMaterialSpannerEdit(spanner, material)) }; +void MetavoxelSystem::setHeightfieldMaterial(const SharedObjectPointer& spanner, + const SharedObjectPointer& material, bool paint) { + MetavoxelEditMessage edit = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(spanner, material, QColor(), paint)) }; applyMaterialEdit(edit, true); } @@ -586,6 +547,12 @@ void MetavoxelSystem::deleteTextures(int heightTextureID, int colorTextureID, in glDeleteTextures(1, (const GLuint*)&materialTextureID); } +void MetavoxelSystem::deleteBuffers(int vertexBufferID, int indexBufferID, int hermiteBufferID) const { + glDeleteBuffers(1, (const GLuint*)&vertexBufferID); + glDeleteBuffers(1, (const GLuint*)&indexBufferID); + glDeleteBuffers(1, (const GLuint*)&hermiteBufferID); +} + class SpannerRenderVisitor : public SpannerVisitor { public: @@ -661,75 +628,6 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - _heightfieldCursorProgram.bind(); - - glActiveTexture(GL_TEXTURE4); - float scale = 1.0f / radius; - glm::vec4 sCoefficients(scale, 0.0f, 0.0f, -scale * position.x); - glm::vec4 tCoefficients(0.0f, 0.0f, scale, -scale * position.z); - glm::vec4 rCoefficients(0.0f, 0.0f, 0.0f, 0.0f); - glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&sCoefficients); - glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&tCoefficients); - glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&rCoefficients); - glActiveTexture(GL_TEXTURE0); - - glm::vec3 extents(radius, radius, radius); - SpannerCursorRenderVisitor visitor(getLOD(), Box(position - extents, position + extents)); - guide(visitor); - - _heightfieldCursorProgram.release(); - - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - - glDisable(GL_POLYGON_OFFSET_FILL); - glDisable(GL_CULL_FACE); - glDepthFunc(GL_LESS); -} - -class BufferCursorRenderVisitor : public MetavoxelVisitor { -public: - - BufferCursorRenderVisitor(const AttributePointer& attribute, const Box& bounds); - - virtual int visit(MetavoxelInfo& info); - -private: - - Box _bounds; -}; - -BufferCursorRenderVisitor::BufferCursorRenderVisitor(const AttributePointer& attribute, const Box& bounds) : - MetavoxelVisitor(QVector() << attribute), - _bounds(bounds) { -} - -int BufferCursorRenderVisitor::visit(MetavoxelInfo& info) { - if (!info.getBounds().intersects(_bounds)) { - return STOP_RECURSION; - } - BufferData* buffer = info.inputValues.at(0).getInlineValue().data(); - if (buffer) { - buffer->render(true); - } - return info.isLeaf ? STOP_RECURSION : DEFAULT_ORDER; -} - -void MetavoxelSystem::renderVoxelCursor(const glm::vec3& position, float radius) { - glDepthFunc(GL_LEQUAL); - glEnable(GL_CULL_FACE); - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -1.0f); - - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - - glEnableClientState(GL_VERTEX_ARRAY); - - _voxelCursorProgram.bind(); - glActiveTexture(GL_TEXTURE4); float scale = 1.0f / radius; glm::vec4 sCoefficients(scale, 0.0f, 0.0f, -scale * position.x); @@ -741,24 +639,79 @@ void MetavoxelSystem::renderVoxelCursor(const glm::vec3& position, float radius) glActiveTexture(GL_TEXTURE0); glm::vec3 extents(radius, radius, radius); - Box bounds(position - extents, position + extents); - BufferCursorRenderVisitor voxelVisitor(Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(), bounds); - guideToAugmented(voxelVisitor); + SpannerCursorRenderVisitor visitor(getLOD(), Box(position - extents, position + extents)); + guide(visitor); - _voxelCursorProgram.release(); + if (!_heightfieldBaseBatches.isEmpty()) { + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); + _heightfieldCursorProgram.bind(); - _heightfieldCursorProgram.bind(); + foreach (const HeightfieldBaseLayerBatch& batch, _heightfieldBaseBatches) { + glPushMatrix(); + glTranslatef(batch.translation.x, batch.translation.y, batch.translation.z); + glm::vec3 axis = glm::axis(batch.rotation); + glRotatef(glm::degrees(glm::angle(batch.rotation)), axis.x, axis.y, axis.z); + glScalef(batch.scale.x, batch.scale.y, batch.scale.z); + + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBufferID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.indexBufferID); + + HeightfieldPoint* point = 0; + glVertexPointer(3, GL_FLOAT, sizeof(HeightfieldPoint), &point->vertex); + glTexCoordPointer(2, GL_FLOAT, sizeof(HeightfieldPoint), &point->textureCoord); + + glBindTexture(GL_TEXTURE_2D, batch.heightTextureID); + + glDrawRangeElements(GL_TRIANGLES, 0, batch.vertexCount - 1, batch.indexCount, GL_UNSIGNED_INT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glPopMatrix(); + } + + _heightfieldCursorProgram.release(); - SpannerCursorRenderVisitor spannerVisitor(getLOD(), bounds); - guide(spannerVisitor); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + } + _heightfieldBaseBatches.clear(); - _heightfieldCursorProgram.release(); + if (!_voxelBaseBatches.isEmpty()) { + glEnableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); + _voxelCursorProgram.bind(); - glDisableClientState(GL_VERTEX_ARRAY); + foreach (const MetavoxelBatch& batch, _voxelBaseBatches) { + glPushMatrix(); + glTranslatef(batch.translation.x, batch.translation.y, batch.translation.z); + glm::vec3 axis = glm::axis(batch.rotation); + glRotatef(glm::degrees(glm::angle(batch.rotation)), axis.x, axis.y, axis.z); + glScalef(batch.scale.x, batch.scale.y, batch.scale.z); + + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBufferID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.indexBufferID); + + VoxelPoint* point = 0; + glVertexPointer(3, GL_FLOAT, sizeof(VoxelPoint), &point->vertex); + + glDrawRangeElements(GL_QUADS, 0, batch.vertexCount - 1, batch.indexCount, GL_UNSIGNED_INT, 0); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glPopMatrix(); + } + + _voxelCursorProgram.release(); + + glDisableClientState(GL_VERTEX_ARRAY); + } + _voxelBaseBatches.clear(); glDisable(GL_POLYGON_OFFSET_FILL); glDisable(GL_CULL_FACE); @@ -1013,14 +966,6 @@ void Augmenter::run() { QMetaObject::invokeMethod(node->getLinkedData(), "setAugmentedData", Q_ARG(const MetavoxelData&, _data)); } -void MetavoxelSystemClient::refreshVoxelData() { - // make it look as if all the colors have changed - MetavoxelData oldData = getAugmentedData(); - oldData.touch(AttributeRegistry::getInstance()->getVoxelColorAttribute()); - - QThreadPool::globalInstance()->start(new Augmenter(_node, _data, oldData, _remoteDataLOD)); -} - void MetavoxelSystemClient::dataChanged(const MetavoxelData& oldData) { MetavoxelClient::dataChanged(oldData); QThreadPool::globalInstance()->start(new Augmenter(_node, _data, getAugmentedData(), _remoteDataLOD)); @@ -1088,25 +1033,34 @@ VoxelBuffer::VoxelBuffer(const QVector& vertices, const QVector _vertices(vertices), _indices(indices), _hermite(hermite), + _hermiteEnabled(Menu::getInstance()->isOptionChecked(MenuOption::DisplayHermiteData)), _quadIndices(quadIndices), _size(size), _vertexCount(vertices.size()), _indexCount(indices.size()), _hermiteCount(hermite.size()), - _indexBuffer(QOpenGLBuffer::IndexBuffer), + _vertexBufferID(0), + _indexBufferID(0), + _hermiteBufferID(0), _materials(materials) { } -bool VoxelBuffer::findFirstRayIntersection(const glm::vec3& entry, const glm::vec3& origin, - const glm::vec3& direction, float& distance) const { +VoxelBuffer::~VoxelBuffer() { + QMetaObject::invokeMethod(Application::getInstance()->getMetavoxels(), "deleteBuffers", Q_ARG(int, _vertexBufferID), + Q_ARG(int, _indexBufferID), Q_ARG(int, _hermiteBufferID)); +} + +bool VoxelBuffer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float boundsDistance, float& distance) const { float highest = _size - 1.0f; - glm::vec3 position = entry * highest; + glm::vec3 position = (origin + direction * boundsDistance) * highest; glm::vec3 floors = glm::floor(position); int max = _size - 2; int x = qMin((int)floors.x, max), y = qMin((int)floors.y, max), z = qMin((int)floors.z, max); forever { - for (QMultiHash::const_iterator it = _quadIndices.constFind(qRgb(x + 1, y + 1, z + 1)); - it != _quadIndices.constEnd(); it++) { + VoxelCoord key(qRgb(x, y, z)); + for (QMultiHash::const_iterator it = _quadIndices.constFind(key); + it != _quadIndices.constEnd() && it.key() == key; it++) { const int* indices = _indices.constData() + *it; if (findRayTriangleIntersection(origin, direction, _vertices.at(indices[0]).vertex, _vertices.at(indices[1]).vertex, _vertices.at(indices[2]).vertex, distance) || @@ -1164,17 +1118,17 @@ bool VoxelBuffer::findFirstRayIntersection(const glm::vec3& entry, const glm::ve return false; } -void VoxelBuffer::render(bool cursor) { - if (!_vertexBuffer.isCreated()) { - _vertexBuffer.create(); - _vertexBuffer.bind(); - _vertexBuffer.allocate(_vertices.constData(), _vertices.size() * sizeof(VoxelPoint)); - _vertexBuffer.release(); +void VoxelBuffer::render(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, bool cursor) { + if (_vertexBufferID == 0) { + glGenBuffers(1, &_vertexBufferID); + glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferID); + glBufferData(GL_ARRAY_BUFFER, _vertices.size() * sizeof(VoxelPoint), _vertices.constData(), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); - _indexBuffer.create(); - _indexBuffer.bind(); - _indexBuffer.allocate(_indices.constData(), _indices.size() * sizeof(int)); - _indexBuffer.release(); + glGenBuffers(1, &_indexBufferID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferID); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices.size() * sizeof(int), _indices.constData(), GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); if (!_materials.isEmpty()) { _networkTextures.resize(_materials.size()); @@ -1189,36 +1143,30 @@ void VoxelBuffer::render(bool cursor) { } } - if (cursor) { - _vertexBuffer.bind(); - _indexBuffer.bind(); - - VoxelPoint* point = 0; - glVertexPointer(3, GL_FLOAT, sizeof(VoxelPoint), &point->vertex); - glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(VoxelPoint), &point->color); - glNormalPointer(GL_BYTE, sizeof(VoxelPoint), &point->normal); - - glDrawRangeElements(GL_QUADS, 0, _vertexCount - 1, _indexCount, GL_UNSIGNED_INT, 0); - - _vertexBuffer.release(); - _indexBuffer.release(); - return; - } - - VoxelBatch baseBatch; - baseBatch.vertexBuffer = &_vertexBuffer; - baseBatch.indexBuffer = &_indexBuffer; + MetavoxelBatch baseBatch; + baseBatch.translation = translation; + baseBatch.rotation = rotation; + baseBatch.scale = scale; + baseBatch.vertexBufferID = _vertexBufferID; + baseBatch.indexBufferID = _indexBufferID; baseBatch.vertexCount = _vertexCount; baseBatch.indexCount = _indexCount; Application::getInstance()->getMetavoxels()->addVoxelBaseBatch(baseBatch); - if (!_materials.isEmpty()) { + if (!(cursor || _materials.isEmpty())) { VoxelSplatBatch splatBatch; - splatBatch.vertexBuffer = &_vertexBuffer; - splatBatch.indexBuffer = &_indexBuffer; + splatBatch.translation = translation; + splatBatch.rotation = rotation; + splatBatch.scale = scale; + splatBatch.vertexBufferID = _vertexBufferID; + splatBatch.indexBufferID = _indexBufferID; splatBatch.vertexCount = _vertexCount; splatBatch.indexCount = _indexCount; - + splatBatch.splatTextureOffset = glm::vec3( + glm::dot(translation, rotation * glm::vec3(1.0f, 0.0f, 0.0f)) / scale.x, + glm::dot(translation, rotation * glm::vec3(0.0f, 1.0f, 0.0f)) / scale.y, + glm::dot(translation, rotation * glm::vec3(0.0f, 0.0f, 1.0f)) / scale.z); + for (int i = 0; i < _materials.size(); i += SPLAT_COUNT) { for (int j = 0; j < SPLAT_COUNT; j++) { int index = i + j; @@ -1226,8 +1174,8 @@ void VoxelBuffer::render(bool cursor) { const NetworkTexturePointer& texture = _networkTextures.at(index); if (texture) { MaterialObject* material = static_cast(_materials.at(index).data()); - splatBatch.splatTextureScalesS[j] = 1.0f / material->getScaleS(); - splatBatch.splatTextureScalesT[j] = 1.0f / material->getScaleT(); + splatBatch.splatTextureScalesS[j] = scale.x / material->getScaleS(); + splatBatch.splatTextureScalesT[j] = scale.z / material->getScaleT(); splatBatch.splatTextureIDs[j] = texture->getID(); } else { @@ -1243,651 +1191,26 @@ void VoxelBuffer::render(bool cursor) { } if (_hermiteCount > 0) { - if (!_hermiteBuffer.isCreated()) { - _hermiteBuffer.create(); - _hermiteBuffer.bind(); - _hermiteBuffer.allocate(_hermite.constData(), _hermite.size() * sizeof(glm::vec3)); - _hermiteBuffer.release(); + if (_hermiteBufferID == 0) { + glGenBuffers(1, &_hermiteBufferID); + glBindBuffer(GL_ARRAY_BUFFER, _hermiteBufferID); + glBufferData(GL_ARRAY_BUFFER, _hermite.size() * sizeof(glm::vec3), _hermite.constData(), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); _hermite.clear(); } HermiteBatch hermiteBatch; - hermiteBatch.vertexBuffer = &_hermiteBuffer; + hermiteBatch.translation = translation; + hermiteBatch.rotation = rotation; + hermiteBatch.scale = scale; + hermiteBatch.vertexBufferID = _hermiteBufferID; hermiteBatch.vertexCount = _hermiteCount; Application::getInstance()->getMetavoxels()->addHermiteBatch(hermiteBatch); } } -BufferDataAttribute::BufferDataAttribute(const QString& name) : - InlineAttribute(name) { -} - -bool BufferDataAttribute::merge(void*& parent, void* children[], bool postRead) const { - *(BufferDataPointer*)&parent = _defaultValue; - for (int i = 0; i < MERGE_COUNT; i++) { - if (decodeInline(children[i])) { - return false; - } - } - return true; -} - -AttributeValue BufferDataAttribute::inherit(const AttributeValue& parentValue) const { - return AttributeValue(parentValue.getAttribute()); -} - DefaultMetavoxelRendererImplementation::DefaultMetavoxelRendererImplementation() { } -class VoxelAugmentVisitor : public MetavoxelVisitor { -public: - - VoxelAugmentVisitor(const MetavoxelLOD& lod); - - virtual int visit(MetavoxelInfo& info); -}; - -VoxelAugmentVisitor::VoxelAugmentVisitor(const MetavoxelLOD& lod) : - MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getVoxelColorAttribute() << - AttributeRegistry::getInstance()->getVoxelMaterialAttribute() << - AttributeRegistry::getInstance()->getVoxelHermiteAttribute(), QVector() << - Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(), lod) { -} - -class EdgeCrossing { -public: - glm::vec3 point; - glm::vec3 normal; - QRgb color; - char material; -}; - -const int MAX_NORMALS_PER_VERTEX = 4; - -class NormalIndex { -public: - int indices[MAX_NORMALS_PER_VERTEX]; - - int getClosestIndex(const glm::vec3& normal, QVector& vertices) const; -}; - -int NormalIndex::getClosestIndex(const glm::vec3& normal, QVector& vertices) const { - int firstIndex = indices[0]; - int closestIndex = firstIndex; - const VoxelPoint& firstVertex = vertices.at(firstIndex); - float closest = normal.x * firstVertex.normal[0] + normal.y * firstVertex.normal[1] + normal.z * firstVertex.normal[2]; - for (int i = 1; i < MAX_NORMALS_PER_VERTEX; i++) { - int index = indices[i]; - if (index == firstIndex) { - break; - } - const VoxelPoint& vertex = vertices.at(index); - float product = normal.x * vertex.normal[0] + normal.y * vertex.normal[1] + normal.z * vertex.normal[2]; - if (product > closest) { - closest = product; - closestIndex = index; - } - } - return closestIndex; -} - -static glm::vec3 safeNormalize(const glm::vec3& vector) { - float length = glm::length(vector); - return (length > 0.0f) ? (vector / length) : vector; -} - -int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { - if (!info.isLeaf) { - return DEFAULT_ORDER; - } - BufferData* buffer = NULL; - VoxelColorDataPointer color = info.inputValues.at(0).getInlineValue(); - VoxelMaterialDataPointer material = info.inputValues.at(1).getInlineValue(); - VoxelHermiteDataPointer hermite = info.inputValues.at(2).getInlineValue(); - - if (color && hermite) { - QVector vertices; - QVector indices; - QVector hermiteSegments; - QMultiHash quadIndices; - - // see http://www.frankpetterson.com/publications/dualcontour/dualcontour.pdf for a description of the - // dual contour algorithm for generating meshes from voxel data using Hermite-tagged edges - const QVector& colorContents = color->getContents(); - const QVector& hermiteContents = hermite->getContents(); - int size = color->getSize(); - int area = size * size; - - // number variables such as offset3 and alpha0 in this function correspond to cube corners, where the x, y, and z - // components are represented as bits in the 0, 1, and 2 position, respectively; hence, alpha0 is the value at - // the minimum x, y, and z corner and alpha7 is the value at the maximum x, y, and z - int offset3 = size + 1; - int offset5 = area + 1; - int offset6 = area + size; - int offset7 = area + size + 1; - - const QRgb* colorZ = colorContents.constData(); - const QRgb* hermiteData = hermiteContents.constData(); - int hermiteStride = hermite->getSize() * VoxelHermiteData::EDGE_COUNT; - int hermiteArea = hermiteStride * hermite->getSize(); - - const char* materialData = material ? material->getContents().constData() : NULL; - - // as we scan down the cube generating vertices between grid points, we remember the indices of the last - // (element, line, section--x, y, z) so that we can connect generated vertices as quads - int expanded = size + 1; - QVector lineIndices(expanded); - QVector lastLineIndices(expanded); - QVector planeIndices(expanded * expanded); - QVector lastPlaneIndices(expanded * expanded); - - const int EDGES_PER_CUBE = 12; - EdgeCrossing crossings[EDGES_PER_CUBE]; - - float highest = size - 1.0f; - float scale = info.size / highest; - const int ALPHA_OFFSET = 24; - bool displayHermite = Menu::getInstance()->isOptionChecked(MenuOption::DisplayHermiteData); - for (int z = 0; z < expanded; z++) { - const QRgb* colorY = colorZ; - for (int y = 0; y < expanded; y++) { - NormalIndex lastIndex; - const QRgb* colorX = colorY; - for (int x = 0; x < expanded; x++) { - int alpha0 = colorX[0] >> ALPHA_OFFSET; - int alpha1 = alpha0, alpha2 = alpha0, alpha4 = alpha0; - int alphaTotal = alpha0; - int possibleTotal = EIGHT_BIT_MAXIMUM; - - // cubes on the edge are two-dimensional: this ensures that their vertices will be shared between - // neighboring blocks, which share only one layer of points - bool middleX = (x != 0 && x != size); - bool middleY = (y != 0 && y != size); - bool middleZ = (z != 0 && z != size); - if (middleZ) { - alphaTotal += (alpha4 = colorX[area] >> ALPHA_OFFSET); - possibleTotal += EIGHT_BIT_MAXIMUM; - } - - int alpha5 = alpha4, alpha6 = alpha4; - if (middleY) { - alphaTotal += (alpha2 = colorX[size] >> ALPHA_OFFSET); - possibleTotal += EIGHT_BIT_MAXIMUM; - - if (middleZ) { - alphaTotal += (alpha6 = colorX[offset6] >> ALPHA_OFFSET); - possibleTotal += EIGHT_BIT_MAXIMUM; - } - } - - int alpha3 = alpha2, alpha7 = alpha6; - if (middleX) { - alphaTotal += (alpha1 = colorX[1] >> ALPHA_OFFSET); - possibleTotal += EIGHT_BIT_MAXIMUM; - - if (middleY) { - alphaTotal += (alpha3 = colorX[offset3] >> ALPHA_OFFSET); - possibleTotal += EIGHT_BIT_MAXIMUM; - - if (middleZ) { - alphaTotal += (alpha7 = colorX[offset7] >> ALPHA_OFFSET); - possibleTotal += EIGHT_BIT_MAXIMUM; - } - } - if (middleZ) { - alphaTotal += (alpha5 = colorX[offset5] >> ALPHA_OFFSET); - possibleTotal += EIGHT_BIT_MAXIMUM; - } - } - if (alphaTotal == 0 || alphaTotal == possibleTotal) { - if (x != 0) { - colorX++; - } - continue; // no corners set/all corners set - } - // the terrifying conditional code that follows checks each cube edge for a crossing, gathering - // its properties (color, material, normal) if one is present; as before, boundary edges are excluded - int clampedX = qMax(x - 1, 0), clampedY = qMax(y - 1, 0), clampedZ = qMax(z - 1, 0); - const QRgb* hermiteBase = hermiteData + clampedZ * hermiteArea + clampedY * hermiteStride + - clampedX * VoxelHermiteData::EDGE_COUNT; - const char* materialBase = materialData ? - (materialData + clampedZ * area + clampedY * size + clampedX) : NULL; - int crossingCount = 0; - if (middleX) { - if (alpha0 != alpha1) { - QRgb hermite = hermiteBase[0]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha0 == 0) { - crossing.color = colorX[1]; - crossing.material = materialBase ? materialBase[1] : 0; - } else { - crossing.color = colorX[0]; - crossing.material = materialBase ? materialBase[0] : 0; - } - crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f, 0.0f); - } - if (middleY) { - if (alpha1 != alpha3) { - QRgb hermite = hermiteBase[VoxelHermiteData::EDGE_COUNT + 1]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha1 == 0) { - crossing.color = colorX[offset3]; - crossing.material = materialBase ? materialBase[offset3] : 0; - } else { - crossing.color = colorX[1]; - crossing.material = materialBase ? materialBase[1] : 0; - } - crossing.point = glm::vec3(1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f); - } - if (alpha2 != alpha3) { - QRgb hermite = hermiteBase[hermiteStride]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha2 == 0) { - crossing.color = colorX[offset3]; - crossing.material = materialBase ? materialBase[offset3] : 0; - } else { - crossing.color = colorX[size]; - crossing.material = materialBase ? materialBase[size] : 0; - } - crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f, 0.0f); - } - if (middleZ) { - if (alpha3 != alpha7) { - QRgb hermite = hermiteBase[hermiteStride + VoxelHermiteData::EDGE_COUNT + 2]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha3 == 0) { - crossing.color = colorX[offset7]; - crossing.material = materialBase ? materialBase[offset7] : 0; - } else { - crossing.color = colorX[offset3]; - crossing.material = materialBase ? materialBase[offset3] : 0; - } - crossing.point = glm::vec3(1.0f, 1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL); - } - if (alpha5 != alpha7) { - QRgb hermite = hermiteBase[hermiteArea + VoxelHermiteData::EDGE_COUNT + 1]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha5 == 0) { - crossing.color = colorX[offset7]; - crossing.material = materialBase ? materialBase[offset7] : 0; - } else { - crossing.color = colorX[offset5]; - crossing.material = materialBase ? materialBase[offset5] : 0; - } - crossing.point = glm::vec3(1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f); - } - if (alpha6 != alpha7) { - QRgb hermite = hermiteBase[hermiteArea + hermiteStride]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha6 == 0) { - crossing.color = colorX[offset7]; - crossing.material = materialBase ? materialBase[offset7] : 0; - } else { - crossing.color = colorX[offset6]; - crossing.material = materialBase ? materialBase[offset6] : 0; - } - crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f, 1.0f); - } - } - } - if (middleZ) { - if (alpha1 != alpha5) { - QRgb hermite = hermiteBase[VoxelHermiteData::EDGE_COUNT + 2]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha1 == 0) { - crossing.color = colorX[offset5]; - crossing.material = materialBase ? materialBase[offset5] : 0; - } else { - crossing.color = colorX[1]; - crossing.material = materialBase ? materialBase[1] : 0; - } - crossing.point = glm::vec3(1.0f, 0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL); - } - if (alpha4 != alpha5) { - QRgb hermite = hermiteBase[hermiteArea]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha4 == 0) { - crossing.color = colorX[offset5]; - crossing.material = materialBase ? materialBase[offset5] : 0; - } else { - crossing.color = colorX[area]; - crossing.material = materialBase ? materialBase[area] : 0; - } - crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f, 1.0f); - } - } - } - if (middleY) { - if (alpha0 != alpha2) { - QRgb hermite = hermiteBase[1]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha0 == 0) { - crossing.color = colorX[size]; - crossing.material = materialBase ? materialBase[size] : 0; - } else { - crossing.color = colorX[0]; - crossing.material = materialBase ? materialBase[0] : 0; - } - crossing.point = glm::vec3(0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f); - } - if (middleZ) { - if (alpha2 != alpha6) { - QRgb hermite = hermiteBase[hermiteStride + 2]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha2 == 0) { - crossing.color = colorX[offset6]; - crossing.material = materialBase ? materialBase[offset6] : 0; - } else { - crossing.color = colorX[size]; - crossing.material = materialBase ? materialBase[size] : 0; - } - crossing.point = glm::vec3(0.0f, 1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL); - } - if (alpha4 != alpha6) { - QRgb hermite = hermiteBase[hermiteArea + 1]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha4 == 0) { - crossing.color = colorX[offset6]; - crossing.material = materialBase ? materialBase[offset6] : 0; - } else { - crossing.color = colorX[area]; - crossing.material = materialBase ? materialBase[area] : 0; - } - crossing.point = glm::vec3(0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f); - } - } - } - if (middleZ && alpha0 != alpha4) { - QRgb hermite = hermiteBase[2]; - EdgeCrossing& crossing = crossings[crossingCount++]; - crossing.normal = unpackNormal(hermite); - if (alpha0 == 0) { - crossing.color = colorX[area]; - crossing.material = materialBase ? materialBase[area] : 0; - } else { - crossing.color = colorX[0]; - crossing.material = materialBase ? materialBase[0] : 0; - } - crossing.point = glm::vec3(0.0f, 0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL); - } - // at present, we simply average the properties of each crossing as opposed to finding the vertex that - // minimizes the quadratic error function as described in the reference paper - glm::vec3 center; - glm::vec3 normals[MAX_NORMALS_PER_VERTEX]; - int normalCount = 0; - const float CREASE_COS_NORMAL = glm::cos(glm::radians(45.0f)); - const int MAX_MATERIALS_PER_VERTEX = 4; - quint8 materials[] = { 0, 0, 0, 0 }; - glm::vec4 materialWeights; - float totalWeight = 0.0f; - int red = 0, green = 0, blue = 0; - for (int i = 0; i < crossingCount; i++) { - const EdgeCrossing& crossing = crossings[i]; - center += crossing.point; - - int j = 0; - for (; j < normalCount; j++) { - if (glm::dot(normals[j], crossing.normal) > CREASE_COS_NORMAL) { - normals[j] = safeNormalize(normals[j] + crossing.normal); - break; - } - } - if (j == normalCount) { - normals[normalCount++] = crossing.normal; - } - - red += qRed(crossing.color); - green += qGreen(crossing.color); - blue += qBlue(crossing.color); - - if (displayHermite) { - glm::vec3 start = info.minimum + (glm::vec3(clampedX, clampedY, clampedZ) + - crossing.point) * scale; - hermiteSegments.append(start); - hermiteSegments.append(start + crossing.normal * scale); - } - - // when assigning a material, search for its presence and, if not found, - // place it in the first empty slot - if (crossing.material != 0) { - for (j = 0; j < MAX_MATERIALS_PER_VERTEX; j++) { - if (materials[j] == crossing.material) { - materialWeights[j] += 1.0f; - totalWeight += 1.0f; - break; - - } else if (materials[j] == 0) { - materials[j] = crossing.material; - materialWeights[j] = 1.0f; - totalWeight += 1.0f; - break; - } - } - } - } - center /= crossingCount; - - // use a sequence of Givens rotations to perform a QR decomposition - // see http://www.cs.rice.edu/~jwarren/papers/techreport02408.pdf - glm::mat4 r(0.0f); - glm::vec4 bottom; - for (int i = 0; i < crossingCount; i++) { - const EdgeCrossing& crossing = crossings[i]; - bottom = glm::vec4(crossing.normal, glm::dot(crossing.normal, crossing.point - center)); - - for (int j = 0; j < 4; j++) { - float angle = glm::atan(-bottom[j], r[j][j]); - float sina = glm::sin(angle); - float cosa = glm::cos(angle); - - for (int k = 0; k < 4; k++) { - float tmp = bottom[k]; - bottom[k] = sina * r[k][j] + cosa * tmp; - r[k][j] = cosa * r[k][j] - sina * tmp; - } - } - } - - // extract the submatrices, form ata - glm::mat3 a(r); - glm::vec3 b(r[3]); - glm::mat3 atrans = glm::transpose(a); - glm::mat3 ata = atrans * a; - - // find the eigenvalues and eigenvectors of ata - // (see http://en.wikipedia.org/wiki/Jacobi_eigenvalue_algorithm) - glm::mat3 d = ata; - glm::quat combinedRotation; - const int MAX_ITERATIONS = 20; - for (int i = 0; i < MAX_ITERATIONS; i++) { - glm::vec3 offDiagonals = glm::abs(glm::vec3(d[1][0], d[2][0], d[2][1])); - int largestIndex = (offDiagonals[0] > offDiagonals[1]) ? (offDiagonals[0] > offDiagonals[2] ? 0 : 2) : - (offDiagonals[1] > offDiagonals[2] ? 1 : 2); - const float DESIRED_PRECISION = 0.00001f; - if (offDiagonals[largestIndex] < DESIRED_PRECISION) { - break; - } - int largestJ = (largestIndex == 2) ? 1 : 0; - int largestI = (largestIndex == 0) ? 1 : 2; - float sjj = d[largestJ][largestJ]; - float sii = d[largestI][largestI]; - float angle = glm::atan(2.0f * d[largestJ][largestI], sjj - sii) / 2.0f; - glm::quat rotation = glm::angleAxis(angle, largestIndex == 0 ? glm::vec3(0.0f, 0.0f, -1.0f) : - (largestIndex == 1 ? glm::vec3(0.0f, 1.0f, 0.0f) : glm::vec3(-1.0f, 0.0f, 0.0f))); - combinedRotation = glm::normalize(rotation * combinedRotation); - glm::mat3 matrix = glm::mat3_cast(combinedRotation); - d = matrix * ata * glm::transpose(matrix); - } - - // form the singular matrix from the eigenvalues - const float MIN_SINGULAR_THRESHOLD = 0.1f; - d[0][0] = (d[0][0] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[0][0]; - d[1][1] = (d[1][1] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[1][1]; - d[2][2] = (d[2][2] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[2][2]; - - // compute the pseudo-inverse, ataplus, and use to find the minimizing solution - glm::mat3 u = glm::mat3_cast(combinedRotation); - glm::mat3 ataplus = glm::transpose(u) * d * u; - glm::vec3 solution = (ataplus * atrans * b) + center; - - // make sure it doesn't fall beyond the cell boundaries - center = glm::clamp(solution, 0.0f, 1.0f); - - if (totalWeight > 0.0f) { - materialWeights *= (EIGHT_BIT_MAXIMUM / totalWeight); - } - VoxelPoint point = { info.minimum + (glm::vec3(clampedX, clampedY, clampedZ) + center) * scale, - { (quint8)(red / crossingCount), (quint8)(green / crossingCount), (quint8)(blue / crossingCount) }, - { (char)(normals[0].x * 127.0f), (char)(normals[0].y * 127.0f), (char)(normals[0].z * 127.0f) }, - { materials[0], materials[1], materials[2], materials[3] }, - { (quint8)materialWeights[0], (quint8)materialWeights[1], (quint8)materialWeights[2], - (quint8)materialWeights[3] } }; - - NormalIndex index = { { vertices.size(), vertices.size(), vertices.size(), vertices.size() } }; - vertices.append(point); - for (int i = 1; i < normalCount; i++) { - index.indices[i] = vertices.size(); - point.setNormal(normals[i]); - vertices.append(point); - } - - // the first x, y, and z are repeated for the boundary edge; past that, we consider generating - // quads for each edge that includes a transition, using indices of previously generated vertices - if (x != 0 && y != 0 && z != 0) { - if (alpha0 != alpha1) { - quadIndices.insert(qRgb(x, y, z), indices.size()); - quadIndices.insert(qRgb(x, y - 1, z), indices.size()); - quadIndices.insert(qRgb(x, y - 1, z - 1), indices.size()); - quadIndices.insert(qRgb(x, y, z - 1), indices.size()); - - const NormalIndex& index1 = lastLineIndices.at(x); - const NormalIndex& index2 = lastPlaneIndices.at((y - 1) * expanded + x); - const NormalIndex& index3 = lastPlaneIndices.at(y * expanded + x); - - const glm::vec3& first = vertices.at(index.indices[0]).vertex; - glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first, - vertices.at(index3.indices[0]).vertex - first); - - if (alpha0 == 0) { // quad faces negative x - indices.append(index3.getClosestIndex(normal = -normal, vertices)); - indices.append(index2.getClosestIndex(normal, vertices)); - indices.append(index1.getClosestIndex(normal, vertices)); - } else { // quad faces positive x - indices.append(index1.getClosestIndex(normal, vertices)); - indices.append(index2.getClosestIndex(normal, vertices)); - indices.append(index3.getClosestIndex(normal, vertices)); - } - indices.append(index.getClosestIndex(normal, vertices)); - } - - if (alpha0 != alpha2) { - quadIndices.insert(qRgb(x, y, z), indices.size()); - quadIndices.insert(qRgb(x - 1, y, z), indices.size()); - quadIndices.insert(qRgb(x - 1, y, z - 1), indices.size()); - quadIndices.insert(qRgb(x, y, z - 1), indices.size()); - - const NormalIndex& index1 = lastIndex; - const NormalIndex& index2 = lastPlaneIndices.at(y * expanded + x - 1); - const NormalIndex& index3 = lastPlaneIndices.at(y * expanded + x); - - const glm::vec3& first = vertices.at(index.indices[0]).vertex; - glm::vec3 normal = glm::cross(vertices.at(index3.indices[0]).vertex - first, - vertices.at(index1.indices[0]).vertex - first); - - if (alpha0 == 0) { // quad faces negative y - indices.append(index1.getClosestIndex(normal = -normal, vertices)); - indices.append(index2.getClosestIndex(normal, vertices)); - indices.append(index3.getClosestIndex(normal, vertices)); - } else { // quad faces positive y - indices.append(index3.getClosestIndex(normal, vertices)); - indices.append(index2.getClosestIndex(normal, vertices)); - indices.append(index1.getClosestIndex(normal, vertices)); - } - indices.append(index.getClosestIndex(normal, vertices)); - } - - if (alpha0 != alpha4) { - quadIndices.insert(qRgb(x, y, z), indices.size()); - quadIndices.insert(qRgb(x - 1, y, z), indices.size()); - quadIndices.insert(qRgb(x - 1, y - 1, z), indices.size()); - quadIndices.insert(qRgb(x, y - 1, z), indices.size()); - - const NormalIndex& index1 = lastIndex; - const NormalIndex& index2 = lastLineIndices.at(x - 1); - const NormalIndex& index3 = lastLineIndices.at(x); - - const glm::vec3& first = vertices.at(index.indices[0]).vertex; - glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first, - vertices.at(index3.indices[0]).vertex - first); - - if (alpha0 == 0) { // quad faces negative z - indices.append(index3.getClosestIndex(normal = -normal, vertices)); - indices.append(index2.getClosestIndex(normal, vertices)); - indices.append(index1.getClosestIndex(normal, vertices)); - } else { // quad faces positive z - indices.append(index1.getClosestIndex(normal, vertices)); - indices.append(index2.getClosestIndex(normal, vertices)); - indices.append(index3.getClosestIndex(normal, vertices)); - } - indices.append(index.getClosestIndex(normal, vertices)); - } - } - lastIndex = index; - lineIndices[x] = index; - planeIndices[y * expanded + x] = index; - - if (x != 0) { - colorX++; - } - } - lineIndices.swap(lastLineIndices); - - if (y != 0) { - colorY += size; - } - } - planeIndices.swap(lastPlaneIndices); - - if (z != 0) { - colorZ += area; - } - } - buffer = new VoxelBuffer(vertices, indices, hermiteSegments, quadIndices, size, - material ? material->getMaterials() : QVector()); - } - BufferDataPointer pointer(buffer); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer)); - return STOP_RECURSION; -} - -void DefaultMetavoxelRendererImplementation::augment(MetavoxelData& data, const MetavoxelData& previous, - MetavoxelInfo& info, const MetavoxelLOD& lod) { - // copy the previous buffers - MetavoxelData expandedPrevious = previous; - while (expandedPrevious.getSize() < data.getSize()) { - expandedPrevious.expand(); - } - const AttributePointer& voxelBufferAttribute = - Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(); - MetavoxelNode* root = expandedPrevious.getRoot(voxelBufferAttribute); - if (root) { - data.setRoot(voxelBufferAttribute, root); - root->incrementReferenceCount(); - } - VoxelAugmentVisitor voxelAugmentVisitor(lod); - data.guideToDifferent(expandedPrevious, voxelAugmentVisitor); -} - class SpannerSimulateVisitor : public SpannerVisitor { public: @@ -1917,59 +1240,14 @@ void DefaultMetavoxelRendererImplementation::simulate(MetavoxelData& data, float data.guide(spannerSimulateVisitor); } -class BufferRenderVisitor : public MetavoxelVisitor { -public: - - BufferRenderVisitor(const AttributePointer& attribute); - - virtual int visit(MetavoxelInfo& info); - -private: - - int _order; - int _containmentDepth; -}; - -BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute) : - MetavoxelVisitor(QVector() << attribute), - _order(encodeOrder(Application::getInstance()->getDisplayViewFrustum()->getDirection())), - _containmentDepth(INT_MAX) { -} - -int BufferRenderVisitor::visit(MetavoxelInfo& info) { - if (_containmentDepth >= _depth) { - Frustum::IntersectionType intersection = Application::getInstance()->getMetavoxels()->getFrustum().getIntersectionType( - info.getBounds()); - if (intersection == Frustum::NO_INTERSECTION) { - return STOP_RECURSION; - } - _containmentDepth = (intersection == Frustum::CONTAINS_INTERSECTION) ? _depth : INT_MAX; - } - if (!info.isLeaf) { - return _order; - } - BufferDataPointer buffer = info.inputValues.at(0).getInlineValue(); - if (buffer) { - buffer->render(); - } - return STOP_RECURSION; -} - void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod) { - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSpanners)) { - SpannerRenderVisitor spannerRenderVisitor(lod); - data.guide(spannerRenderVisitor); - } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderDualContourSurfaces)) { - BufferRenderVisitor voxelRenderVisitor(Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute()); - data.guide(voxelRenderVisitor); - } + SpannerRenderVisitor spannerRenderVisitor(lod); + data.guide(spannerRenderVisitor); } SphereRenderer::SphereRenderer() { } - void SphereRenderer::render(const MetavoxelLOD& lod, bool contained, bool cursor) { Sphere* sphere = static_cast(_spanner); const QColor& color = sphere->getColor(); @@ -2126,6 +1404,141 @@ HeightfieldNodeRenderer::~HeightfieldNodeRenderer() { Q_ARG(int, _colorTextureID), Q_ARG(int, _materialTextureID)); } +bool HeightfieldNodeRenderer::findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, + const glm::vec3& scale, const glm::vec3& origin, const glm::vec3& direction, + float boundsDistance, float& distance) const { + if (!_voxels) { + return false; + } + glm::quat inverseRotation = glm::inverse(rotation); + float inverseScale = 1.0f / scale.x; + return static_cast(_voxels.data())->findRayIntersection( + inverseRotation * (origin - translation) * inverseScale, inverseRotation * direction * inverseScale, + boundsDistance, distance); +} + +class EdgeCrossing { +public: + glm::vec3 point; + glm::vec3 normal; + QRgb color; + char material; + + void setColorMaterial(const StackArray::Entry& entry) { color = entry.color; material = entry.material; } +}; + +const int MAX_NORMALS_PER_VERTEX = 4; + +class NormalIndex { +public: + int indices[MAX_NORMALS_PER_VERTEX]; + + bool isValid() const; + + int getClosestIndex(const glm::vec3& normal, QVector& vertices) const; +}; + +bool NormalIndex::isValid() const { + for (int i = 0; i < MAX_NORMALS_PER_VERTEX; i++) { + if (indices[i] >= 0) { + return true; + } + } + return false; +} + +int NormalIndex::getClosestIndex(const glm::vec3& normal, QVector& vertices) const { + int firstIndex = indices[0]; + int closestIndex = firstIndex; + const VoxelPoint& firstVertex = vertices.at(firstIndex); + float closest = normal.x * firstVertex.normal[0] + normal.y * firstVertex.normal[1] + normal.z * firstVertex.normal[2]; + for (int i = 1; i < MAX_NORMALS_PER_VERTEX; i++) { + int index = indices[i]; + if (index == firstIndex) { + break; + } + const VoxelPoint& vertex = vertices.at(index); + float product = normal.x * vertex.normal[0] + normal.y * vertex.normal[1] + normal.z * vertex.normal[2]; + if (product > closest) { + closest = product; + closestIndex = index; + } + } + return closestIndex; +} + +static glm::vec3 safeNormalize(const glm::vec3& vector) { + float length = glm::length(vector); + return (length > 0.0f) ? (vector / length) : vector; +} + +class IndexVector : public QVector { +public: + + int position; + + void swap(IndexVector& other) { QVector::swap(other); qSwap(position, other.position); } + + const NormalIndex& get(int y) const; + const NormalIndex& getClosest(int y) const; +}; + +const NormalIndex& IndexVector::get(int y) const { + static NormalIndex invalidIndex = { { -1, -1, -1, -1 } }; + int relative = y - position; + return (relative >= 0 && relative < size()) ? at(relative) : invalidIndex; +} + +const NormalIndex& IndexVector::getClosest(int y) const { + static NormalIndex invalidIndex = { { -1, -1, -1, -1 } }; + int relative = y - position; + if (relative < 0 || relative >= size()) { + return invalidIndex; + } + const NormalIndex& first = at(relative); + if (first.isValid()) { + return first; + } + for (int distance = 1; relative - distance >= 0 || relative + distance < size(); distance++) { + int previous = relative - distance; + if (previous >= 0) { + const NormalIndex& previousIndex = at(previous); + if (previousIndex.isValid()) { + return previousIndex; + } + } + int next = relative + distance; + if (next < size()) { + const NormalIndex& nextIndex = at(next); + if (nextIndex.isValid()) { + return nextIndex; + } + } + } + return invalidIndex; +} + +static inline void appendIndices(QVector& indices, QMultiHash& quadIndices, + const QVector& vertices, float step, int i0, int i1, int i2, int i3) { + int newIndices[] = { i0, i1, i2, i3 }; + glm::vec3 minima(FLT_MAX, FLT_MAX, FLT_MAX), maxima(-FLT_MAX, -FLT_MAX, -FLT_MAX); + int indexIndex = indices.size(); + for (unsigned int i = 0; i < sizeof(newIndices) / sizeof(newIndices[0]); i++) { + int index = newIndices[i]; + indices.append(index); + const glm::vec3& vertex = vertices.at(index).vertex; + minima = glm::min(vertex, minima); + maxima = glm::max(vertex, maxima); + } + for (int z = (int)minima.z, endZ = (int)glm::ceil(maxima.z); z < endZ; z++) { + for (int y = (int)minima.x, endY = (int)glm::ceil(maxima.y); y < endY; y++) { + for (int x = (int)minima.x, endX = (int)glm::ceil(maxima.x); x < endX; x++) { + quadIndices.insert(qRgb(x, y, z), indexIndex); + } + } + } +} + void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, bool cursor) { if (!node->getHeight()) { @@ -2199,9 +1612,8 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - const QVector& heightContents = node->getHeight()->getContents(); glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, width, height, 0, - GL_RED, GL_UNSIGNED_SHORT, heightContents.constData()); + GL_RED, GL_UNSIGNED_SHORT, node->getHeight()->getContents().constData()); glGenTextures(1, &_colorTextureID); glBindTexture(GL_TEXTURE_2D, _colorTextureID); @@ -2250,36 +1662,743 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g // restore the default alignment; it's what Qt uses for image storage glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } - - if (cursor) { - bufferPair.first.bind(); - bufferPair.second.bind(); - - glPushMatrix(); - glTranslatef(translation.x, translation.y, translation.z); - glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - glScalef(scale.x, scale.y, scale.z); + bool displayHermite = Menu::getInstance()->isOptionChecked(MenuOption::DisplayHermiteData); + if ((!_voxels || (displayHermite && !static_cast(_voxels.data())->isHermiteEnabled())) && node->getStack()) { + // see http://www.frankpetterson.com/publications/dualcontour/dualcontour.pdf for a description of the + // dual contour algorithm for generating meshes from voxel data using Hermite-tagged edges - HeightfieldPoint* point = 0; - glVertexPointer(3, GL_FLOAT, sizeof(HeightfieldPoint), &point->vertex); - glTexCoordPointer(2, GL_FLOAT, sizeof(HeightfieldPoint), &point->textureCoord); + QVector vertices; + QVector indices; + QVector hermiteSegments; + QMultiHash quadIndices; - glBindTexture(GL_TEXTURE_2D, _heightTextureID); + int stackWidth = node->getStack()->getWidth(); + int stackHeight = node->getStack()->getContents().size() / stackWidth; + int innerStackWidth = stackWidth - HeightfieldData::SHARED_EDGE; + int innerStackHeight = stackHeight - HeightfieldData::SHARED_EDGE; + const StackArray* src = node->getStack()->getContents().constData(); + const quint16* heightSrc = node->getHeight()->getContents().constData() + + (width + 1) * HeightfieldHeight::HEIGHT_BORDER; + QVector stackMaterials = node->getStack()->getMaterials(); + QHash materialMap; - glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + int colorWidth; + const uchar* colorSrc = NULL; + float colorStepX, colorStepZ; + if (node->getColor()) { + colorWidth = node->getColor()->getWidth(); + int colorHeight = node->getColor()->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); + colorSrc = (const uchar*)node->getColor()->getContents().constData(); + colorStepX = (colorWidth - HeightfieldData::SHARED_EDGE) / (float)innerStackWidth; + colorStepZ = (colorHeight - HeightfieldData::SHARED_EDGE) / (float)innerStackHeight; + } - glBindTexture(GL_TEXTURE_2D, 0); + int materialWidth; + const uchar* materialSrc = NULL; + float materialStepX, materialStepZ; + if (node->getMaterial()) { + materialWidth = node->getMaterial()->getWidth(); + int materialHeight = node->getMaterial()->getContents().size() / materialWidth; + materialSrc = (const uchar*)node->getMaterial()->getContents().constData(); + materialStepX = (materialWidth - HeightfieldData::SHARED_EDGE) / (float)innerStackWidth; + materialStepZ = (materialHeight - HeightfieldData::SHARED_EDGE) / (float)innerStackHeight; + } - glPopMatrix(); + const int EDGES_PER_CUBE = 12; + EdgeCrossing crossings[EDGES_PER_CUBE]; - bufferPair.first.release(); - bufferPair.second.release(); - return; + // as we scan down the cube generating vertices between grid points, we remember the indices of the last + // (element, line, section--x, y, z) so that we can connect generated vertices as quads + IndexVector indicesX; + IndexVector lastIndicesX; + QVector indicesZ(stackWidth + 1); + QVector lastIndicesZ(stackWidth + 1); + float step = 1.0f / innerStackWidth; + float voxelScale = scale.y / (numeric_limits::max() * scale.x * step); + + for (int z = 0; z <= stackHeight; z++) { + bool middleZ = (z != 0 && z != stackHeight); + const StackArray* lineSrc = src; + const quint16* heightLineSrc = heightSrc; + for (int x = 0; x <= stackWidth; x++) { + bool middleX = (x != 0 && x != stackWidth); + + // find the y extents of this and the neighboring columns + int minimumY = INT_MAX, maximumY = -1; + lineSrc->getExtents(minimumY, maximumY); + if (middleX) { + lineSrc[1].getExtents(minimumY, maximumY); + if (middleZ) { + lineSrc[stackWidth + 1].getExtents(minimumY, maximumY); + } + } + if (middleZ) { + lineSrc[stackWidth].getExtents(minimumY, maximumY); + } + if (maximumY >= minimumY) { + int position = minimumY; + int count = maximumY - minimumY + 1; + NormalIndex lastIndexY = { { -1, -1, -1, -1 } }; + indicesX.position = position; + indicesX.resize(count); + indicesZ[x].position = position; + indicesZ[x].resize(count); + float heightfieldHeight = *heightLineSrc * voxelScale; + float nextHeightfieldHeightX = heightLineSrc[1] * voxelScale; + float nextHeightfieldHeightZ = heightLineSrc[width] * voxelScale; + float nextHeightfieldHeightXZ = heightLineSrc[width + 1] * voxelScale; + const int UPPER_LEFT_CORNER = 1; + const int UPPER_RIGHT_CORNER = 2; + const int LOWER_LEFT_CORNER = 4; + const int LOWER_RIGHT_CORNER = 8; + const int NO_CORNERS = 0; + const int ALL_CORNERS = UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER; + int corners = NO_CORNERS; + if (heightfieldHeight != 0.0f) { + corners |= UPPER_LEFT_CORNER; + } + if (nextHeightfieldHeightX != 0.0f && x != stackWidth) { + corners |= UPPER_RIGHT_CORNER; + } + if (nextHeightfieldHeightZ != 0.0f && z != stackHeight) { + corners |= LOWER_LEFT_CORNER; + } + if (nextHeightfieldHeightXZ != 0.0f && x != stackWidth && z != stackHeight) { + corners |= LOWER_RIGHT_CORNER; + } + bool stitchable = x != 0 && z != 0 && !(corners == NO_CORNERS || corners == ALL_CORNERS); + VoxelPoint cornerPoints[4]; + int clampedX = qMax(x - 1, 0), clampedZ = qMax(z - 1, 0); + if (stitchable) { + for (unsigned int i = 0; i < sizeof(cornerPoints) / sizeof(cornerPoints[0]); i++) { + if (!(corners & (1 << i))) { + continue; + } + int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0; + int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0; + const quint16* height = heightLineSrc + offsetZ * width + offsetX; + VoxelPoint& point = cornerPoints[i]; + int clampedOffsetX = clampedX + offsetX, clampedOffsetZ = clampedZ + offsetZ; + point.vertex = glm::vec3(clampedOffsetX, *height * voxelScale, clampedOffsetZ) * step; + int left = height[-1]; + int right = height[1]; + int down = height[-width]; + int up = height[width]; + glm::vec3 normal = glm::normalize(glm::vec3((left == 0 || right == 0) ? 0.0f : left - right, + 2.0f / voxelScale, (up == 0 || down == 0) ? 0.0f : down - up)); + point.normal[0] = normal.x * numeric_limits::max(); + point.normal[1] = normal.y * numeric_limits::max(); + point.normal[2] = normal.z * numeric_limits::max(); + if (colorSrc) { + const uchar* color = colorSrc + ((int)(clampedOffsetZ * colorStepZ) * colorWidth + + (int)(clampedOffsetX * colorStepX)) * DataBlock::COLOR_BYTES; + point.color[0] = color[0]; + point.color[1] = color[1]; + point.color[2] = color[2]; + + } else { + point.color[0] = point.color[1] = point.color[2] = numeric_limits::max(); + } + int material = 0; + if (materialSrc) { + material = materialSrc[(int)(clampedOffsetZ * materialStepZ) * materialWidth + + (int)(clampedOffsetX * materialStepX)]; + if (material != 0) { + int& mapping = materialMap[material]; + if (mapping == 0) { + mapping = getMaterialIndex(node->getMaterial()->getMaterials().at(material - 1), + stackMaterials); + } + material = mapping; + } + } + point.materials[0] = material; + point.materials[1] = point.materials[2] = point.materials[3] = 0; + point.materialWeights[0] = numeric_limits::max(); + point.materialWeights[1] = point.materialWeights[2] = point.materialWeights[3] = 0; + } + } + for (int y = position, end = position + count; y < end; y++) { + const StackArray::Entry& entry = lineSrc->getEntry(y, heightfieldHeight); + if (displayHermite && x != 0 && z != 0 && !lineSrc->isEmpty() && y >= lineSrc->getPosition()) { + glm::vec3 normal; + if (entry.hermiteX != 0) { + glm::vec3 start = glm::vec3(clampedX + entry.getHermiteX(normal), y, clampedZ) * step; + hermiteSegments.append(start); + hermiteSegments.append(start + normal * step); + } + if (entry.hermiteY != 0) { + glm::vec3 start = glm::vec3(clampedX, y + entry.getHermiteY(normal), clampedZ) * step; + hermiteSegments.append(start); + hermiteSegments.append(start + normal * step); + } + if (entry.hermiteZ != 0) { + glm::vec3 start = glm::vec3(clampedX, y, clampedZ + entry.getHermiteZ(normal)) * step; + hermiteSegments.append(start); + hermiteSegments.append(start + normal * step); + } + } + // number variables correspond to cube corners, where the x, y, and z components are represented as + // bits in the 0, 1, and 2 position, respectively; hence, alpha0 is the value at the minimum x, y, and + // z corner and alpha7 is the value at the maximum x, y, and z + int alpha0 = lineSrc->getEntryAlpha(y, heightfieldHeight); + int alpha2 = lineSrc->getEntryAlpha(y + 1, heightfieldHeight); + int alpha1 = alpha0, alpha3 = alpha2, alpha4 = alpha0, alpha6 = alpha2; + int alphaTotal = alpha0 + alpha2; + int possibleTotal = 2 * numeric_limits::max(); + + // cubes on the edge are two-dimensional: this ensures that their vertices will be shared between + // neighboring blocks, which share only one layer of points + if (middleZ) { + alphaTotal += (alpha4 = lineSrc[stackWidth].getEntryAlpha(y, nextHeightfieldHeightZ)); + possibleTotal += numeric_limits::max(); + + alphaTotal += (alpha6 = lineSrc[stackWidth].getEntryAlpha(y + 1, nextHeightfieldHeightZ)); + possibleTotal += numeric_limits::max(); + } + int alpha5 = alpha4, alpha7 = alpha6; + if (middleX) { + alphaTotal += (alpha1 = lineSrc[1].getEntryAlpha(y, nextHeightfieldHeightX)); + possibleTotal += numeric_limits::max(); + + alphaTotal += (alpha3 = lineSrc[1].getEntryAlpha(y + 1, nextHeightfieldHeightX)); + possibleTotal += numeric_limits::max(); + + if (middleZ) { + alphaTotal += (alpha5 = lineSrc[stackWidth + 1].getEntryAlpha(y, nextHeightfieldHeightXZ)); + possibleTotal += numeric_limits::max(); + + alphaTotal += (alpha7 = lineSrc[stackWidth + 1].getEntryAlpha(y + 1, nextHeightfieldHeightXZ)); + possibleTotal += numeric_limits::max(); + } + } + if (alphaTotal == 0 || alphaTotal == possibleTotal) { + continue; // no corners set/all corners set + } + // the terrifying conditional code that follows checks each cube edge for a crossing, gathering + // its properties (color, material, normal) if one is present; as before, boundary edges are excluded + int crossingCount = 0; + const StackArray::Entry& nextEntryY = lineSrc->getEntry(y + 1, heightfieldHeight); + if (middleX) { + const StackArray::Entry& nextEntryX = lineSrc[1].getEntry(y, nextHeightfieldHeightX); + const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX); + if (alpha0 != alpha1) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(entry.getHermiteX(crossing.normal), 0.0f, 0.0f); + crossing.setColorMaterial(alpha0 == 0 ? nextEntryX : entry); + } + if (alpha1 != alpha3) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(1.0f, nextEntryX.getHermiteY(crossing.normal), 0.0f); + crossing.setColorMaterial(alpha1 == 0 ? nextEntryXY : nextEntryX); + } + if (alpha2 != alpha3) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(nextEntryY.getHermiteX(crossing.normal), 1.0f, 0.0f); + crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY); + } + if (middleZ) { + const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y, nextHeightfieldHeightZ); + const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry( + y, nextHeightfieldHeightXZ); + const StackArray::Entry& nextEntryXYZ = lineSrc[stackWidth + 1].getEntry( + y + 1, nextHeightfieldHeightXZ); + if (alpha1 != alpha5) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(1.0f, 0.0f, nextEntryX.getHermiteZ(crossing.normal)); + crossing.setColorMaterial(alpha1 == 0 ? nextEntryXZ : nextEntryX); + } + if (alpha3 != alpha7) { + EdgeCrossing& crossing = crossings[crossingCount++]; + const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX); + crossing.point = glm::vec3(1.0f, 1.0f, nextEntryXY.getHermiteZ(crossing.normal)); + crossing.setColorMaterial(alpha3 == 0 ? nextEntryXYZ : nextEntryXY); + } + if (alpha4 != alpha5) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(nextEntryZ.getHermiteX(crossing.normal), 0.0f, 1.0f); + crossing.setColorMaterial(alpha4 == 0 ? nextEntryXZ : nextEntryZ); + } + if (alpha5 != alpha7) { + EdgeCrossing& crossing = crossings[crossingCount++]; + const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry( + y, nextHeightfieldHeightXZ); + crossing.point = glm::vec3(1.0f, nextEntryXZ.getHermiteY(crossing.normal), 1.0f); + crossing.setColorMaterial(alpha5 == 0 ? nextEntryXYZ : nextEntryXZ); + } + if (alpha6 != alpha7) { + EdgeCrossing& crossing = crossings[crossingCount++]; + const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry( + y + 1, nextHeightfieldHeightZ); + crossing.point = glm::vec3(nextEntryYZ.getHermiteX(crossing.normal), 1.0f, 1.0f); + crossing.setColorMaterial(alpha6 == 0 ? nextEntryXYZ : nextEntryYZ); + } + } + } + if (alpha0 != alpha2) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(0.0f, entry.getHermiteY(crossing.normal), 0.0f); + crossing.setColorMaterial(alpha0 == 0 ? nextEntryY : entry); + } + if (middleZ) { + const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y, nextHeightfieldHeightZ); + const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(y + 1, nextHeightfieldHeightZ); + if (alpha0 != alpha4) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal)); + crossing.setColorMaterial(alpha0 == 0 ? nextEntryZ : entry); + } + if (alpha2 != alpha6) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(0.0f, 1.0f, nextEntryY.getHermiteZ(crossing.normal)); + crossing.setColorMaterial(alpha2 == 0 ? nextEntryYZ : nextEntryY); + } + if (alpha4 != alpha6) { + EdgeCrossing& crossing = crossings[crossingCount++]; + crossing.point = glm::vec3(0.0f, nextEntryZ.getHermiteY(crossing.normal), 1.0f); + crossing.setColorMaterial(alpha4 == 0 ? nextEntryYZ : nextEntryZ); + } + } + // determine whether we should ignore this vertex because it will be stitched + int validCrossings = 0; + for (int i = 0; i < crossingCount; i++) { + if (qAlpha(crossings[i].color) != 0) { + validCrossings++; + } + } + NormalIndex index = { { -1, -1, -1, -1 } }; + if (validCrossings != 0) { + index.indices[0] = index.indices[1] = index.indices[2] = index.indices[3] = vertices.size(); + glm::vec3 center; + glm::vec3 normals[MAX_NORMALS_PER_VERTEX]; + int normalCount = 0; + const float CREASE_COS_NORMAL = glm::cos(glm::radians(45.0f)); + const int MAX_MATERIALS_PER_VERTEX = 4; + quint8 materials[] = { 0, 0, 0, 0 }; + glm::vec4 materialWeights; + float totalWeight = 0.0f; + int red = 0, green = 0, blue = 0; + for (int i = 0; i < crossingCount; i++) { + const EdgeCrossing& crossing = crossings[i]; + if (qAlpha(crossing.color) == 0) { + continue; + } + center += crossing.point; + + int j = 0; + for (; j < normalCount; j++) { + if (glm::dot(normals[j], crossing.normal) > CREASE_COS_NORMAL) { + normals[j] = safeNormalize(normals[j] + crossing.normal); + break; + } + } + if (j == normalCount) { + normals[normalCount++] = crossing.normal; + } + + red += qRed(crossing.color); + green += qGreen(crossing.color); + blue += qBlue(crossing.color); + + // when assigning a material, search for its presence and, if not found, + // place it in the first empty slot + if (crossing.material != 0) { + for (j = 0; j < MAX_MATERIALS_PER_VERTEX; j++) { + if (materials[j] == crossing.material) { + materialWeights[j] += 1.0f; + totalWeight += 1.0f; + break; + + } else if (materials[j] == 0) { + materials[j] = crossing.material; + materialWeights[j] = 1.0f; + totalWeight += 1.0f; + break; + } + } + } + } + center /= validCrossings; + + // use a sequence of Givens rotations to perform a QR decomposition + // see http://www.cs.rice.edu/~jwarren/papers/techreport02408.pdf + glm::mat4 r(0.0f); + glm::vec4 bottom; + for (int i = 0; i < crossingCount; i++) { + const EdgeCrossing& crossing = crossings[i]; + if (qAlpha(crossing.color) == 0) { + continue; + } + bottom = glm::vec4(crossing.normal, glm::dot(crossing.normal, crossing.point - center)); + + for (int j = 0; j < 4; j++) { + float angle = glm::atan(-bottom[j], r[j][j]); + float sina = glm::sin(angle); + float cosa = glm::cos(angle); + + for (int k = 0; k < 4; k++) { + float tmp = bottom[k]; + bottom[k] = sina * r[k][j] + cosa * tmp; + r[k][j] = cosa * r[k][j] - sina * tmp; + } + } + } + + // extract the submatrices, form ata + glm::mat3 a(r); + glm::vec3 b(r[3]); + glm::mat3 atrans = glm::transpose(a); + glm::mat3 ata = atrans * a; + + // find the eigenvalues and eigenvectors of ata + // (see http://en.wikipedia.org/wiki/Jacobi_eigenvalue_algorithm) + glm::mat3 d = ata; + glm::quat combinedRotation; + const int MAX_ITERATIONS = 20; + for (int i = 0; i < MAX_ITERATIONS; i++) { + glm::vec3 offDiagonals = glm::abs(glm::vec3(d[1][0], d[2][0], d[2][1])); + int largestIndex = (offDiagonals[0] > offDiagonals[1]) ? + (offDiagonals[0] > offDiagonals[2] ? 0 : 2) : (offDiagonals[1] > offDiagonals[2] ? 1 : 2); + const float DESIRED_PRECISION = 0.00001f; + if (offDiagonals[largestIndex] < DESIRED_PRECISION) { + break; + } + int largestJ = (largestIndex == 2) ? 1 : 0; + int largestI = (largestIndex == 0) ? 1 : 2; + float sjj = d[largestJ][largestJ]; + float sii = d[largestI][largestI]; + float angle = glm::atan(2.0f * d[largestJ][largestI], sjj - sii) / 2.0f; + glm::quat rotation = glm::angleAxis(angle, largestIndex == 0 ? glm::vec3(0.0f, 0.0f, -1.0f) : + (largestIndex == 1 ? glm::vec3(0.0f, 1.0f, 0.0f) : glm::vec3(-1.0f, 0.0f, 0.0f))); + combinedRotation = glm::normalize(rotation * combinedRotation); + glm::mat3 matrix = glm::mat3_cast(combinedRotation); + d = matrix * ata * glm::transpose(matrix); + } + + // form the singular matrix from the eigenvalues + const float MIN_SINGULAR_THRESHOLD = 0.1f; + d[0][0] = (d[0][0] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[0][0]; + d[1][1] = (d[1][1] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[1][1]; + d[2][2] = (d[2][2] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[2][2]; + + // compute the pseudo-inverse, ataplus, and use to find the minimizing solution + glm::mat3 u = glm::mat3_cast(combinedRotation); + glm::mat3 ataplus = glm::transpose(u) * d * u; + glm::vec3 solution = (ataplus * atrans * b) + center; + + // make sure it doesn't fall beyond the cell boundaries + center = glm::clamp(solution, 0.0f, 1.0f); + + if (totalWeight > 0.0f) { + materialWeights *= (numeric_limits::max() / totalWeight); + } + VoxelPoint point = { (glm::vec3(clampedX, y, clampedZ) + center) * step, + { (quint8)(red / validCrossings), (quint8)(green / validCrossings), + (quint8)(blue / validCrossings) }, + { (char)(normals[0].x * 127.0f), (char)(normals[0].y * 127.0f), + (char)(normals[0].z * 127.0f) }, + { materials[0], materials[1], materials[2], materials[3] }, + { (quint8)materialWeights[0], (quint8)materialWeights[1], (quint8)materialWeights[2], + (quint8)materialWeights[3] } }; + + vertices.append(point); + for (int i = 1; i < normalCount; i++) { + index.indices[i] = vertices.size(); + point.setNormal(normals[i]); + vertices.append(point); + } + + if (stitchable) { + int nextIndex = vertices.size(); + const NormalIndex& previousIndexX = lastIndicesX.getClosest(y); + const NormalIndex& previousIndexZ = lastIndicesZ[x].getClosest(y); + switch (corners) { + case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER: { + vertices.append(cornerPoints[0]); + vertices.append(cornerPoints[3]); + glm::vec3 normal = glm::cross(cornerPoints[0].vertex - cornerPoints[1].vertex, + cornerPoints[3].vertex - cornerPoints[1].vertex); + int firstIndex = index.getClosestIndex(normal, vertices); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex + 1, nextIndex, nextIndex); + if (previousIndexX.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + nextIndex, previousIndexX.getClosestIndex(normal, vertices)); + } + break; + } + case UPPER_LEFT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER: { + vertices.append(cornerPoints[0]); + vertices.append(cornerPoints[3]); + glm::vec3 normal = glm::cross(cornerPoints[3].vertex - cornerPoints[2].vertex, + cornerPoints[0].vertex - cornerPoints[2].vertex); + int firstIndex = index.getClosestIndex(normal, vertices); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex, nextIndex + 1, nextIndex + 1); + if (previousIndexZ.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + previousIndexZ.getClosestIndex(normal, vertices), nextIndex); + } + break; + } + case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER: { + vertices.append(cornerPoints[1]); + vertices.append(cornerPoints[2]); + vertices.append(cornerPoints[3]); + glm::vec3 normal = glm::cross(cornerPoints[3].vertex - cornerPoints[2].vertex, + cornerPoints[1].vertex - cornerPoints[2].vertex); + int firstIndex = index.getClosestIndex(normal, vertices); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex + 2, nextIndex, nextIndex); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex + 1, nextIndex + 2, nextIndex + 2); + if (previousIndexX.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + previousIndexX.getClosestIndex(normal, vertices), nextIndex + 1); + } + if (previousIndexZ.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + nextIndex, previousIndexZ.getClosestIndex(normal, vertices)); + } + break; + } + case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER: { + vertices.append(cornerPoints[0]); + vertices.append(cornerPoints[1]); + vertices.append(cornerPoints[2]); + glm::vec3 normal = glm::cross(cornerPoints[2].vertex - cornerPoints[0].vertex, + cornerPoints[1].vertex - cornerPoints[0].vertex); + int firstIndex = index.getClosestIndex(normal, vertices); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex + 1, nextIndex, nextIndex); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex, nextIndex + 2, nextIndex + 2); + break; + } + case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER: { + vertices.append(cornerPoints[0]); + vertices.append(cornerPoints[1]); + const glm::vec3& first = vertices.at(index.indices[0]).vertex; + glm::vec3 normal = glm::cross(cornerPoints[1].vertex - first, + cornerPoints[0].vertex - first); + int firstIndex = index.getClosestIndex(normal, vertices); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex + 1, nextIndex, nextIndex); + if (previousIndexX.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + nextIndex, previousIndexX.getClosestIndex(normal, vertices)); + } + break; + } + case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER: { + vertices.append(cornerPoints[1]); + vertices.append(cornerPoints[3]); + const glm::vec3& first = vertices.at(index.indices[0]).vertex; + glm::vec3 normal = glm::cross(cornerPoints[3].vertex - first, + cornerPoints[1].vertex - first); + int firstIndex = index.getClosestIndex(normal, vertices); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex + 1, nextIndex, nextIndex); + if (previousIndexZ.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, + firstIndex, nextIndex, previousIndexZ.getClosestIndex(normal, vertices)); + } + break; + } + case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER: { + vertices.append(cornerPoints[3]); + vertices.append(cornerPoints[2]); + const glm::vec3& first = vertices.at(index.indices[0]).vertex; + glm::vec3 normal = glm::cross(cornerPoints[2].vertex - first, + cornerPoints[3].vertex - first); + int firstIndex = index.getClosestIndex(normal, vertices); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex + 1, nextIndex, nextIndex); + if (previousIndexX.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + previousIndexX.getClosestIndex(normal, vertices), nextIndex + 1); + } + break; + } + case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER: { + vertices.append(cornerPoints[2]); + vertices.append(cornerPoints[0]); + const glm::vec3& first = vertices.at(index.indices[0]).vertex; + glm::vec3 normal = glm::cross(cornerPoints[0].vertex - first, + cornerPoints[2].vertex - first); + int firstIndex = index.getClosestIndex(normal, vertices); + appendIndices(indices, quadIndices, vertices, step, firstIndex, + nextIndex + 1, nextIndex, nextIndex); + if (previousIndexZ.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + previousIndexZ.getClosestIndex(normal, vertices), nextIndex + 1); + } + break; + } + case UPPER_LEFT_CORNER: { + vertices.append(cornerPoints[0]); + glm::vec3 normal = glm::cross(cornerPoints[0].vertex - + vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f)); + int firstIndex = index.getClosestIndex(normal, vertices); + if (previousIndexX.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + nextIndex, previousIndexX.getClosestIndex(normal, vertices)); + } + if (previousIndexZ.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + previousIndexZ.getClosestIndex(normal, vertices), nextIndex); + } + break; + } + case UPPER_RIGHT_CORNER: { + vertices.append(cornerPoints[1]); + glm::vec3 normal = glm::cross(cornerPoints[1].vertex - + vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f)); + int firstIndex = index.getClosestIndex(normal, vertices); + if (previousIndexZ.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + nextIndex, previousIndexZ.getClosestIndex(normal, vertices)); + } + break; + } + case LOWER_LEFT_CORNER: { + vertices.append(cornerPoints[2]); + glm::vec3 normal = glm::cross(cornerPoints[2].vertex - + vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f)); + int firstIndex = index.getClosestIndex(normal, vertices); + if (previousIndexX.isValid()) { + appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex, + previousIndexX.getClosestIndex(normal, vertices), nextIndex); + } + break; + } + } + } + } + + // the first x, y, and z are repeated for the boundary edge; past that, we consider generating + // quads for each edge that includes a transition, using indices of previously generated vertices + int reclampedX = qMin(clampedX, stackWidth - 1); + int reclampedZ = qMin(clampedZ, stackHeight - 1); + if (alpha0 != alpha1 && y > position && z > 0) { + const NormalIndex& index1 = lastIndexY; + const NormalIndex& index2 = lastIndicesZ[x].get(y - 1); + const NormalIndex& index3 = lastIndicesZ[x].get(y); + if (index.isValid() && index1.isValid() && index2.isValid() && index3.isValid()) { + quadIndices.insert(qRgb(reclampedX, y, reclampedZ), indices.size()); + quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ), indices.size()); + if (reclampedZ > 0) { + quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ - 1), indices.size()); + quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size()); + } + const glm::vec3& first = vertices.at(index.indices[0]).vertex; + glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first, + vertices.at(index3.indices[0]).vertex - first); + + if (alpha0 == 0) { // quad faces negative x + indices.append(index3.getClosestIndex(normal = -normal, vertices)); + indices.append(index2.getClosestIndex(normal, vertices)); + indices.append(index1.getClosestIndex(normal, vertices)); + } else { // quad faces positive x + indices.append(index1.getClosestIndex(normal, vertices)); + indices.append(index2.getClosestIndex(normal, vertices)); + indices.append(index3.getClosestIndex(normal, vertices)); + } + indices.append(index.getClosestIndex(normal, vertices)); + } + } + + if (alpha0 != alpha2 && x > 0 && z > 0) { + const NormalIndex& index1 = lastIndicesZ[x].get(y); + const NormalIndex& index2 = lastIndicesZ[x - 1].get(y); + const NormalIndex& index3 = lastIndicesX.get(y); + if (index.isValid() && index1.isValid() && index2.isValid() && index3.isValid()) { + quadIndices.insert(qRgb(reclampedX, y, reclampedZ), indices.size()); + if (reclampedX > 0) { + quadIndices.insert(qRgb(reclampedX - 1, y, reclampedZ), indices.size()); + if (reclampedZ > 0) { + quadIndices.insert(qRgb(reclampedX - 1, y, reclampedZ - 1), indices.size()); + } + } + if (reclampedZ > 0) { + quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size()); + } + const glm::vec3& first = vertices.at(index.indices[0]).vertex; + glm::vec3 normal = glm::cross(vertices.at(index3.indices[0]).vertex - first, + vertices.at(index1.indices[0]).vertex - first); + + if (alpha0 == 0) { // quad faces negative y + indices.append(index3.getClosestIndex(normal, vertices)); + indices.append(index2.getClosestIndex(normal, vertices)); + indices.append(index1.getClosestIndex(normal, vertices)); + } else { // quad faces positive y + indices.append(index1.getClosestIndex(normal = -normal, vertices)); + indices.append(index2.getClosestIndex(normal, vertices)); + indices.append(index3.getClosestIndex(normal, vertices)); + } + indices.append(index.getClosestIndex(normal, vertices)); + } + } + + if (alpha0 != alpha4 && x > 0 && y > position) { + const NormalIndex& index1 = lastIndexY; + const NormalIndex& index2 = lastIndicesX.get(y - 1); + const NormalIndex& index3 = lastIndicesX.get(y); + if (index.isValid() && index1.isValid() && index2.isValid() && index3.isValid()) { + quadIndices.insert(qRgb(reclampedX, y, reclampedZ), indices.size()); + if (reclampedX > 0) { + quadIndices.insert(qRgb(reclampedX - 1, y, reclampedZ), indices.size()); + quadIndices.insert(qRgb(reclampedX - 1, y - 1, reclampedZ), indices.size()); + } + quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ), indices.size()); + + const glm::vec3& first = vertices.at(index.indices[0]).vertex; + glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first, + vertices.at(index3.indices[0]).vertex - first); + + if (alpha0 == 0) { // quad faces negative z + indices.append(index1.getClosestIndex(normal, vertices)); + indices.append(index2.getClosestIndex(normal, vertices)); + indices.append(index3.getClosestIndex(normal, vertices)); + } else { // quad faces positive z + indices.append(index3.getClosestIndex(normal = -normal, vertices)); + indices.append(index2.getClosestIndex(normal, vertices)); + indices.append(index1.getClosestIndex(normal, vertices)); + } + indices.append(index.getClosestIndex(normal, vertices)); + } + } + lastIndexY = index; + indicesX[y - position] = index; + indicesZ[x][y - position] = index; + } + } else { + indicesX.clear(); + indicesZ[x].clear(); + } + if (x != 0) { + lineSrc++; + heightLineSrc++; + } + indicesX.swap(lastIndicesX); + } + if (z != 0) { + src += stackWidth; + heightSrc += width; + } + indicesZ.swap(lastIndicesZ); + lastIndicesX.clear(); + } + _voxels = new VoxelBuffer(vertices, indices, hermiteSegments, quadIndices, stackWidth, stackMaterials); } + + if (_voxels) { + _voxels->render(translation, rotation, glm::vec3(scale.x, scale.x, scale.x), cursor); + } + HeightfieldBaseLayerBatch baseBatch; - baseBatch.vertexBuffer = &bufferPair.first; - baseBatch.indexBuffer = &bufferPair.second; + baseBatch.vertexBufferID = bufferPair.first.bufferId(); + baseBatch.indexBufferID = bufferPair.second.bufferId(); baseBatch.translation = translation; baseBatch.rotation = rotation; baseBatch.scale = scale; @@ -2289,12 +2408,12 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g baseBatch.heightScale = glm::vec4(1.0f / width, 1.0f / height, (innerWidth - 1) / -2.0f, (innerHeight - 1) / -2.0f); baseBatch.colorTextureID = _colorTextureID; baseBatch.colorScale = glm::vec2((float)width / innerWidth, (float)height / innerHeight); - Application::getInstance()->getMetavoxels()->addHeightfieldBaseBatch(baseBatch); + Application::getInstance()->getMetavoxels()->addHeightfieldBaseBatch(baseBatch); - if (!_networkTextures.isEmpty()) { + if (!(cursor || _networkTextures.isEmpty())) { HeightfieldSplatBatch splatBatch; - splatBatch.vertexBuffer = &bufferPair.first; - splatBatch.indexBuffer = &bufferPair.second; + splatBatch.vertexBufferID = bufferPair.first.bufferId(); + splatBatch.indexBufferID = bufferPair.second.bufferId(); splatBatch.translation = translation; splatBatch.rotation = rotation; splatBatch.scale = scale; diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index a21cd2c1a4..28e5758840 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -26,8 +26,8 @@ class HeightfieldBaseLayerBatch; class HeightfieldSplatBatch; class HermiteBatch; +class MetavoxelBatch; class Model; -class VoxelBatch; class VoxelSplatBatch; /// Renders a metavoxel tree. @@ -59,48 +59,35 @@ public: void setNetworkSimulation(const NetworkSimulation& simulation); NetworkSimulation getNetworkSimulation(); - const AttributePointer& getHeightfieldBufferAttribute() { return _heightfieldBufferAttribute; } - const AttributePointer& getVoxelBufferAttribute() { return _voxelBufferAttribute; } - void simulate(float deltaTime); void render(); void renderHeightfieldCursor(const glm::vec3& position, float radius); - void renderVoxelCursor(const glm::vec3& position, float radius); - - bool findFirstRayVoxelIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance); - Q_INVOKABLE void paintHeightfieldColor(const glm::vec3& position, float radius, const QColor& color); Q_INVOKABLE void paintHeightfieldMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material); - Q_INVOKABLE void paintVoxelColor(const glm::vec3& position, float radius, const QColor& color); - - Q_INVOKABLE void paintVoxelMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material); + Q_INVOKABLE void setHeightfieldColor(const SharedObjectPointer& spanner, const QColor& color, bool paint = false); - Q_INVOKABLE void setVoxelColor(const SharedObjectPointer& spanner, const QColor& color); - - Q_INVOKABLE void setVoxelMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material); + Q_INVOKABLE void setHeightfieldMaterial(const SharedObjectPointer& spanner, + const SharedObjectPointer& material, bool paint = false); void addHeightfieldBaseBatch(const HeightfieldBaseLayerBatch& batch) { _heightfieldBaseBatches.append(batch); } void addHeightfieldSplatBatch(const HeightfieldSplatBatch& batch) { _heightfieldSplatBatches.append(batch); } - void addVoxelBaseBatch(const VoxelBatch& batch) { _voxelBaseBatches.append(batch); } + void addVoxelBaseBatch(const MetavoxelBatch& batch) { _voxelBaseBatches.append(batch); } void addVoxelSplatBatch(const VoxelSplatBatch& batch) { _voxelSplatBatches.append(batch); } void addHermiteBatch(const HermiteBatch& batch) { _hermiteBatches.append(batch); } Q_INVOKABLE void deleteTextures(int heightTextureID, int colorTextureID, int materialTextureID) const; - + Q_INVOKABLE void deleteBuffers(int vertexBufferID, int indexBufferID, int hermiteBufferID) const; + signals: void rendering(); -public slots: - - void refreshVoxelData(); - protected: Q_INVOKABLE void applyMaterialEdit(const MetavoxelEditMessage& message, bool reliable = false); @@ -111,9 +98,6 @@ private: void guideToAugmented(MetavoxelVisitor& visitor, bool render = false); - AttributePointer _heightfieldBufferAttribute; - AttributePointer _voxelBufferAttribute; - MetavoxelLOD _lod; QReadWriteLock _lodLock; Frustum _frustum; @@ -123,7 +107,7 @@ private: QVector _heightfieldBaseBatches; QVector _heightfieldSplatBatches; - QVector _voxelBaseBatches; + QVector _voxelBaseBatches; QVector _voxelSplatBatches; QVector _hermiteBatches; @@ -166,16 +150,21 @@ private: static void loadSplatProgram(const char* type, ProgramObject& program, SplatLocations& locations); }; -/// Base class for heightfield batches. -class HeightfieldBatch { +/// Base class for all batches. +class MetavoxelBatch { public: - QOpenGLBuffer* vertexBuffer; - QOpenGLBuffer* indexBuffer; + GLuint vertexBufferID; + GLuint indexBufferID; glm::vec3 translation; glm::quat rotation; glm::vec3 scale; int vertexCount; int indexCount; +}; + +/// Base class for heightfield batches. +class HeightfieldBatch : public MetavoxelBatch { +public: GLuint heightTextureID; glm::vec4 heightScale; }; @@ -199,18 +188,10 @@ public: int materialIndex; }; -/// Base class for voxel batches. -class VoxelBatch { -public: - QOpenGLBuffer* vertexBuffer; - QOpenGLBuffer* indexBuffer; - int vertexCount; - int indexCount; -}; - /// A batch containing a voxel splat. -class VoxelSplatBatch : public VoxelBatch { +class VoxelSplatBatch : public MetavoxelBatch { public: + glm::vec3 splatTextureOffset; int splatTextureIDs[4]; glm::vec4 splatTextureScalesS; glm::vec4 splatTextureScalesT; @@ -220,7 +201,10 @@ public: /// A batch containing Hermite data for debugging. class HermiteBatch { public: - QOpenGLBuffer* vertexBuffer; + GLuint vertexBufferID; + glm::vec3 translation; + glm::quat rotation; + glm::vec3 scale; int vertexCount; }; @@ -271,8 +255,6 @@ public: void setRenderedAugmentedData(const MetavoxelData& data) { _renderedAugmentedData = data; } virtual int parseData(const QByteArray& packet); - - Q_INVOKABLE void refreshVoxelData(); protected: @@ -295,7 +277,8 @@ public: virtual ~BufferData(); - virtual void render(bool cursor = false) = 0; + virtual void render(const glm::vec3& translation, const glm::quat& rotation, + const glm::vec3& scale, bool cursor = false) = 0; }; typedef QExplicitlySharedDataPointer BufferDataPointer; @@ -334,44 +317,34 @@ public: VoxelBuffer(const QVector& vertices, const QVector& indices, const QVector& hermite, const QMultiHash& quadIndices, int size, const QVector& materials = QVector()); + virtual ~VoxelBuffer(); + + bool isHermiteEnabled() const { return _hermiteEnabled; } /// Finds the first intersection between the described ray and the voxel data. - /// \param entry the entry point of the ray in relative coordinates, from (0, 0, 0) to (1, 1, 1) - bool findFirstRayIntersection(const glm::vec3& entry, const glm::vec3& origin, - const glm::vec3& direction, float& distance) const; + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const; - virtual void render(bool cursor = false); + virtual void render(const glm::vec3& translation, const glm::quat& rotation, + const glm::vec3& scale, bool cursor = false); private: QVector _vertices; QVector _indices; QVector _hermite; + bool _hermiteEnabled; QMultiHash _quadIndices; int _size; int _vertexCount; int _indexCount; int _hermiteCount; - QOpenGLBuffer _vertexBuffer; - QOpenGLBuffer _indexBuffer; - QOpenGLBuffer _hermiteBuffer; + GLuint _vertexBufferID; + GLuint _indexBufferID; + GLuint _hermiteBufferID; QVector _materials; QVector _networkTextures; }; -/// A client-side attribute that stores renderable buffers. -class BufferDataAttribute : public InlineAttribute { - Q_OBJECT - -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. class DefaultMetavoxelRendererImplementation : public MetavoxelRendererImplementation { Q_OBJECT @@ -380,7 +353,6 @@ public: Q_INVOKABLE DefaultMetavoxelRendererImplementation(); - virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod); virtual void simulate(MetavoxelData& data, float deltaTime, MetavoxelInfo& info, const MetavoxelLOD& lod); virtual void render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod); }; @@ -450,6 +422,9 @@ public: HeightfieldNodeRenderer(); virtual ~HeightfieldNodeRenderer(); + virtual bool findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const; + void render(const HeightfieldNodePointer& node, const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, bool cursor); @@ -460,6 +435,8 @@ private: GLuint _materialTextureID; QVector _networkTextures; + BufferDataPointer _voxels; + typedef QPair IntPair; typedef QPair BufferPair; static QHash _bufferPairs; diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 23ff960aea..1df82a42a0 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -97,7 +97,7 @@ MetavoxelEditor::MetavoxelEditor() : _gridSpacing->setMinimum(-FLT_MAX); _gridSpacing->setMaximum(FLT_MAX); _gridSpacing->setPrefix("2^"); - _gridSpacing->setValue(-3.0); + _gridSpacing->setValue(0.0); connect(_gridSpacing, SIGNAL(valueChanged(double)), SLOT(alignGridPosition())); formLayout->addRow("Grid Position:", _gridPosition = new QDoubleSpinBox()); @@ -125,13 +125,13 @@ MetavoxelEditor::MetavoxelEditor() : addTool(new InsertSpannerTool(this)); addTool(new RemoveSpannerTool(this)); addTool(new ClearSpannersTool(this)); + addTool(new ImportHeightfieldTool(this)); addTool(new HeightfieldHeightBrushTool(this)); addTool(new HeightfieldMaterialBrushTool(this)); - addTool(new ImportHeightfieldTool(this)); - addTool(new VoxelMaterialBoxTool(this)); - addTool(new VoxelMaterialSpannerTool(this)); - addTool(new VoxelMaterialBrushTool(this)); - addTool(new VoxelSculptBrushTool(this)); + addTool(new HeightfieldSculptBrushTool(this)); + addTool(new HeightfieldFillBrushTool(this)); + addTool(new HeightfieldMaterialBoxTool(this)); + addTool(new HeightfieldMaterialSpannerTool(this)); updateAttributes(); @@ -331,6 +331,9 @@ void MetavoxelEditor::render() { MetavoxelTool* tool = getActiveTool(); if (tool) { tool->render(); + if (!tool->getUsesGrid()) { + return; + } } glDepthMask(GL_FALSE); @@ -386,10 +389,11 @@ MetavoxelTool* MetavoxelEditor::getActiveTool() const { ProgramObject MetavoxelEditor::_gridProgram; -MetavoxelTool::MetavoxelTool(MetavoxelEditor* editor, const QString& name, bool usesValue, bool userFacing) : +MetavoxelTool::MetavoxelTool(MetavoxelEditor* editor, const QString& name, bool usesValue, bool userFacing, bool usesGrid) : _editor(editor), _usesValue(usesValue), - _userFacing(userFacing) { + _userFacing(userFacing), + _usesGrid(usesGrid) { QVBoxLayout* layout = new QVBoxLayout(); setLayout(layout); @@ -670,7 +674,7 @@ void InsertSpannerTool::applyEdit(const AttributePointer& attribute, const Share } RemoveSpannerTool::RemoveSpannerTool(MetavoxelEditor* editor) : - MetavoxelTool(editor, "Remove Spanner", false) { + MetavoxelTool(editor, "Remove Spanner", false, true, false) { } bool RemoveSpannerTool::appliesTo(const AttributePointer& attribute) const { @@ -697,7 +701,7 @@ bool RemoveSpannerTool::eventFilter(QObject* watched, QEvent* event) { } ClearSpannersTool::ClearSpannersTool(MetavoxelEditor* editor) : - MetavoxelTool(editor, "Clear Spanners", false) { + MetavoxelTool(editor, "Clear Spanners", false, true, false) { QPushButton* button = new QPushButton("Clear"); layout()->addWidget(button); @@ -718,7 +722,7 @@ void ClearSpannersTool::clear() { } HeightfieldTool::HeightfieldTool(MetavoxelEditor* editor, const QString& name) : - MetavoxelTool(editor, name, false) { + MetavoxelTool(editor, name, false, true, false) { QWidget* widget = new QWidget(); widget->setLayout(_form = new QFormLayout()); @@ -807,7 +811,7 @@ void ImportHeightfieldTool::updateSpanner() { } HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QString& name) : - MetavoxelTool(editor, name, false), + MetavoxelTool(editor, name, false, true, false), _positionValid(false) { QWidget* widget = new QWidget(); @@ -817,7 +821,7 @@ HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QStrin _form->addRow("Radius:", _radius = new QDoubleSpinBox()); _radius->setSingleStep(0.01); _radius->setMaximum(FLT_MAX); - _radius->setValue(1.0); + _radius->setValue(5.0); } bool HeightfieldBrushTool::appliesTo(const AttributePointer& attribute) const { @@ -865,11 +869,19 @@ HeightfieldHeightBrushTool::HeightfieldHeightBrushTool(MetavoxelEditor* editor) _height->setMinimum(-FLT_MAX); _height->setMaximum(FLT_MAX); _height->setValue(1.0); + + _form->addRow("Mode:", _mode = new QComboBox()); + _mode->addItem("Raise/Lower"); + _mode->addItem("Set"); + _mode->addItem("Erase"); } QVariant HeightfieldHeightBrushTool::createEdit(bool alternate) { + const int SET_MODE_INDEX = 1; + const int ERASE_MODE_INDEX = 2; return QVariant::fromValue(PaintHeightfieldHeightEdit(_position, _radius->value(), - alternate ? -_height->value() : _height->value())); + alternate ? -_height->value() : _height->value(), _mode->currentIndex() == SET_MODE_INDEX, + _mode->currentIndex() == ERASE_MODE_INDEX)); } MaterialControl::MaterialControl(QWidget* widget, QFormLayout* form, bool clearable) : @@ -939,16 +951,58 @@ HeightfieldMaterialBrushTool::HeightfieldMaterialBrushTool(MetavoxelEditor* edit } QVariant HeightfieldMaterialBrushTool::createEdit(bool alternate) { + Sphere* sphere = new Sphere(); + sphere->setTranslation(_position); + sphere->setScale(_radius->value()); if (alternate) { - return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), SharedObjectPointer(), QColor())); + return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), + SharedObjectPointer(), QColor(0, 0, 0, 0), true)); } else { - return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), _materialControl->getMaterial(), - _materialControl->getColor())); - } + return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), + _materialControl->getMaterial(), _materialControl->getColor(), true)); + } } -VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) : - BoxTool(editor, "Set Voxel Material (Box)", false) { +HeightfieldSculptBrushTool::HeightfieldSculptBrushTool(MetavoxelEditor* editor) : + HeightfieldBrushTool(editor, "Sculpt Brush"), + _materialControl(new MaterialControl(this, _form, true)) { +} + +QVariant HeightfieldSculptBrushTool::createEdit(bool alternate) { + Sphere* sphere = new Sphere(); + sphere->setTranslation(_position); + sphere->setScale(_radius->value()); + if (alternate) { + return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), + SharedObjectPointer(), QColor(0, 0, 0, 0))); + } else { + return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), + _materialControl->getMaterial(), _materialControl->getColor())); + } +} + +HeightfieldFillBrushTool::HeightfieldFillBrushTool(MetavoxelEditor* editor) : + HeightfieldBrushTool(editor, "Fill Brush") { + + _form->addRow("Mode:", _mode = new QComboBox()); + _mode->addItem("Fill"); + _mode->addItem("Voxelize"); +} + +QVariant HeightfieldFillBrushTool::createEdit(bool alternate) { + const int FILL_MODE_INDEX = 0; + if (_mode->currentIndex() == FILL_MODE_INDEX) { + return QVariant::fromValue(FillHeightfieldHeightEdit(_position, _radius->value())); + } + Sphere* sphere = new Sphere(); + sphere->setTranslation(_position); + sphere->setScale(_radius->value()); + return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), + SharedObjectPointer(), QColor(), false, true)); +} + +HeightfieldMaterialBoxTool::HeightfieldMaterialBoxTool(MetavoxelEditor* editor) : + BoxTool(editor, "Set Material (Box)", false) { QWidget* widget = new QWidget(); QFormLayout* form = new QFormLayout(); @@ -965,32 +1019,32 @@ VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) : _materialControl = new MaterialControl(this, form, true); } -bool VoxelMaterialBoxTool::appliesTo(const AttributePointer& attribute) const { - return attribute->inherits("VoxelColorAttribute"); +bool HeightfieldMaterialBoxTool::appliesTo(const AttributePointer& attribute) const { + return attribute->inherits("SpannerSetAttribute"); } -bool VoxelMaterialBoxTool::shouldSnapToGrid() { +bool HeightfieldMaterialBoxTool::shouldSnapToGrid() { return _snapToGrid->isChecked(); } -QColor VoxelMaterialBoxTool::getColor() { +QColor HeightfieldMaterialBoxTool::getColor() { return _materialControl->getColor(); } -void VoxelMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) { +void HeightfieldMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) { Cuboid* cuboid = new Cuboid(); cuboid->setTranslation((maximum + minimum) * 0.5f); glm::vec3 vector = (maximum - minimum) * 0.5f; cuboid->setScale(vector.x); cuboid->setAspectY(vector.y / vector.x); cuboid->setAspectZ(vector.z / vector.x); - MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(SharedObjectPointer(cuboid), + MetavoxelEditMessage message = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(cuboid), _materialControl->getMaterial(), _materialControl->getColor())) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } -VoxelMaterialSpannerTool::VoxelMaterialSpannerTool(MetavoxelEditor* editor) : - PlaceSpannerTool(editor, "Set Voxel Material (Spanner)", QString(), false) { +HeightfieldMaterialSpannerTool::HeightfieldMaterialSpannerTool(MetavoxelEditor* editor) : + PlaceSpannerTool(editor, "Set Material (Spanner)", QString(), false) { QWidget* widget = new QWidget(); QFormLayout* form = new QFormLayout(); @@ -1004,110 +1058,25 @@ VoxelMaterialSpannerTool::VoxelMaterialSpannerTool(MetavoxelEditor* editor) : QPushButton* place = new QPushButton("Set"); layout()->addWidget(place); - connect(place, &QPushButton::clicked, this, &VoxelMaterialSpannerTool::place); + connect(place, &QPushButton::clicked, this, &HeightfieldMaterialSpannerTool::place); } -bool VoxelMaterialSpannerTool::appliesTo(const AttributePointer& attribute) const { - return attribute->inherits("VoxelColorAttribute"); +bool HeightfieldMaterialSpannerTool::appliesTo(const AttributePointer& attribute) const { + return attribute->inherits("SpannerSetAttribute"); } -SharedObjectPointer VoxelMaterialSpannerTool::getSpanner() { +SharedObjectPointer HeightfieldMaterialSpannerTool::getSpanner() { return _spannerEditor->getObject(); } -QColor VoxelMaterialSpannerTool::getColor() { +QColor HeightfieldMaterialSpannerTool::getColor() { return _materialControl->getColor(); } -void VoxelMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { +void HeightfieldMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { static_cast(spanner.data())->setWillBeVoxelized(true); - MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(spanner, + MetavoxelEditMessage message = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(spanner, _materialControl->getMaterial(), _materialControl->getColor())) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } -VoxelBrushTool::VoxelBrushTool(MetavoxelEditor* editor, const QString& name) : - MetavoxelTool(editor, name, false, true), - _positionValid(false) { - - QWidget* widget = new QWidget(); - widget->setLayout(_form = new QFormLayout()); - layout()->addWidget(widget); - - _form->addRow("Radius:", _radius = new QDoubleSpinBox()); - _radius->setSingleStep(0.01); - _radius->setMaximum(FLT_MAX); - _radius->setValue(0.25); -} - -bool VoxelBrushTool::appliesTo(const AttributePointer& attribute) const { - return attribute->inherits("VoxelColorAttribute"); -} - -void VoxelBrushTool::render() { - if (Application::getInstance()->isMouseHidden()) { - return; - } - - // find the intersection with the voxels - glm::vec3 origin = Application::getInstance()->getMouseRayOrigin(); - glm::vec3 direction = Application::getInstance()->getMouseRayDirection(); - - float heightfieldDistance = FLT_MAX, voxelDistance = FLT_MAX; - if (!(Application::getInstance()->getMetavoxels()->findFirstRayHeightfieldIntersection( - origin, direction, heightfieldDistance) | - Application::getInstance()->getMetavoxels()->findFirstRayVoxelIntersection(origin, direction, voxelDistance))) { - _positionValid = false; - return; - } - _positionValid = true; - Application::getInstance()->getMetavoxels()->renderVoxelCursor( - _position = origin + qMin(heightfieldDistance, voxelDistance) * direction, _radius->value()); -} - -bool VoxelBrushTool::eventFilter(QObject* watched, QEvent* event) { - if (event->type() == QEvent::Wheel) { - float angle = static_cast(event)->angleDelta().y(); - const float ANGLE_SCALE = 1.0f / 1000.0f; - _radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE)); - return true; - - } else if (event->type() == QEvent::MouseButtonPress && _positionValid) { - MetavoxelEditMessage message = { createEdit(static_cast(event)->button() == Qt::RightButton) }; - Application::getInstance()->getMetavoxels()->applyEdit(message, true); - return true; - } - return false; -} - -VoxelMaterialBrushTool::VoxelMaterialBrushTool(MetavoxelEditor* editor) : - VoxelBrushTool(editor, "Material Brush"), - _materialControl(new MaterialControl(this, _form)) { -} - -QVariant VoxelMaterialBrushTool::createEdit(bool alternate) { - if (alternate) { - return QVariant::fromValue(PaintVoxelMaterialEdit(_position, _radius->value(), SharedObjectPointer(), QColor())); - } else { - return QVariant::fromValue(PaintVoxelMaterialEdit(_position, _radius->value(), - _materialControl->getMaterial(), _materialControl->getColor())); - } -} - -VoxelSculptBrushTool::VoxelSculptBrushTool(MetavoxelEditor* editor) : - VoxelBrushTool(editor, "Sculpt Brush"), - _materialControl(new MaterialControl(this, _form, true)) { -} - -QVariant VoxelSculptBrushTool::createEdit(bool alternate) { - Sphere* sphere = new Sphere(); - sphere->setTranslation(_position); - sphere->setScale(_radius->value()); - if (alternate) { - return QVariant::fromValue(VoxelMaterialSpannerEdit(SharedObjectPointer(sphere), - SharedObjectPointer(), QColor(0, 0, 0, 0))); - } else { - return QVariant::fromValue(VoxelMaterialSpannerEdit(SharedObjectPointer(sphere), - _materialControl->getMaterial(), _materialControl->getColor())); - } -} diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 391b01270a..23feb940c9 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -92,12 +92,15 @@ class MetavoxelTool : public QWidget { public: - MetavoxelTool(MetavoxelEditor* editor, const QString& name, bool usesValue = true, bool userFacing = true); + MetavoxelTool(MetavoxelEditor* editor, const QString& name, bool usesValue = true, + bool userFacing = true, bool usesGrid = true); bool getUsesValue() const { return _usesValue; } bool isUserFacing() const { return _userFacing; } + bool getUsesGrid() const { return _usesGrid; } + virtual bool appliesTo(const AttributePointer& attribute) const; virtual void simulate(float deltaTime); @@ -113,6 +116,7 @@ protected: MetavoxelEditor* _editor; bool _usesValue; bool _userFacing; + bool _usesGrid; }; /// Base class for tools that allow dragging out a 3D box. @@ -342,6 +346,7 @@ protected: private: QDoubleSpinBox* _height; + QComboBox* _mode; }; /// Contains widgets for editing materials. @@ -387,13 +392,47 @@ private: MaterialControl* _materialControl; }; -/// Allows setting voxel materials by dragging out a box. -class VoxelMaterialBoxTool : public BoxTool { +/// Allows sculpting parts of the heightfield. +class HeightfieldSculptBrushTool : public HeightfieldBrushTool { Q_OBJECT public: - VoxelMaterialBoxTool(MetavoxelEditor* editor); + HeightfieldSculptBrushTool(MetavoxelEditor* editor); + +protected: + + virtual QVariant createEdit(bool alternate); + +private: + + MaterialControl* _materialControl; +}; + +/// Allows "filling" (removing dual contour stack data) parts of the heightfield. +class HeightfieldFillBrushTool : public HeightfieldBrushTool { + Q_OBJECT + +public: + + HeightfieldFillBrushTool(MetavoxelEditor* editor); + +protected: + + virtual QVariant createEdit(bool alternate); + +private: + + QComboBox* _mode; +}; + +/// Allows setting heightfield materials by dragging out a box. +class HeightfieldMaterialBoxTool : public BoxTool { + Q_OBJECT + +public: + + HeightfieldMaterialBoxTool(MetavoxelEditor* editor); virtual bool appliesTo(const AttributePointer& attribute) const; @@ -411,13 +450,13 @@ private: MaterialControl* _materialControl; }; -/// Allows setting voxel materials by placing a spanner. -class VoxelMaterialSpannerTool : public PlaceSpannerTool { +/// Allows setting heightfield materials by placing a spanner. +class HeightfieldMaterialSpannerTool : public PlaceSpannerTool { Q_OBJECT public: - VoxelMaterialSpannerTool(MetavoxelEditor* editor); + HeightfieldMaterialSpannerTool(MetavoxelEditor* editor); virtual bool appliesTo(const AttributePointer& attribute) const; @@ -433,63 +472,4 @@ private: MaterialControl* _materialControl; }; -/// Base class for voxel brush tools. -class VoxelBrushTool : public MetavoxelTool { - Q_OBJECT - -public: - - VoxelBrushTool(MetavoxelEditor* editor, const QString& name); - - virtual bool appliesTo(const AttributePointer& attribute) const; - - virtual void render(); - - virtual bool eventFilter(QObject* watched, QEvent* event); - -protected: - - virtual QVariant createEdit(bool alternate) = 0; - - QFormLayout* _form; - QDoubleSpinBox* _radius; - - glm::vec3 _position; - bool _positionValid; -}; - -/// Allows texturing parts of the voxel field. -class VoxelMaterialBrushTool : public VoxelBrushTool { - Q_OBJECT - -public: - - VoxelMaterialBrushTool(MetavoxelEditor* editor); - -protected: - - virtual QVariant createEdit(bool alternate); - -private: - - MaterialControl* _materialControl; -}; - -/// Allows sculpting parts of the voxel field. -class VoxelSculptBrushTool : public VoxelBrushTool { - Q_OBJECT - -public: - - VoxelSculptBrushTool(MetavoxelEditor* editor); - -protected: - - virtual QVariant createEdit(bool alternate); - -private: - - MaterialControl* _materialControl; -}; - #endif // hifi_MetavoxelEditor_h diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index afdc0c923f..b23d83de55 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -20,13 +20,9 @@ #include "Spanner.h" REGISTER_META_OBJECT(FloatAttribute) -REGISTER_META_OBJECT(MaterialObject) REGISTER_META_OBJECT(SharedObjectAttribute) REGISTER_META_OBJECT(SharedObjectSetAttribute) REGISTER_META_OBJECT(SpannerSetAttribute) -REGISTER_META_OBJECT(VoxelColorAttribute) -REGISTER_META_OBJECT(VoxelHermiteAttribute) -REGISTER_META_OBJECT(VoxelMaterialAttribute) static int attributePointerMetaTypeId = qRegisterMetaType(); static int ownedAttributeValueMetaTypeId = qRegisterMetaType(); @@ -41,21 +37,12 @@ AttributeRegistry::AttributeRegistry() : new DefaultMetavoxelGuide()))), _rendererAttribute(registerAttribute(new SharedObjectAttribute("renderer", &MetavoxelRenderer::staticMetaObject, new DefaultMetavoxelRenderer()))), - _spannersAttribute(registerAttribute(new SpannerSetAttribute("spanners", &Spanner::staticMetaObject))), - _voxelColorAttribute(registerAttribute(new VoxelColorAttribute("voxelColor"))), - _voxelMaterialAttribute(registerAttribute(new VoxelMaterialAttribute("voxelMaterial"))), - _voxelHermiteAttribute(registerAttribute(new VoxelHermiteAttribute("voxelHermite"))) { + _spannersAttribute(registerAttribute(new SpannerSetAttribute("spanners", &Spanner::staticMetaObject))) { - // our baseline LOD threshold is for voxels; spanners and heightfields are a different story - const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f; + // our baseline LOD threshold is for voxels; spanners are a different story + const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 16.0f; _spannersAttribute->setLODThresholdMultiplier(SPANNER_LOD_THRESHOLD_MULTIPLIER); _spannersAttribute->setUserFacing(true); - - const float VOXEL_LOD_THRESHOLD_MULTIPLIER = 16.0f; - _voxelColorAttribute->setLODThresholdMultiplier(VOXEL_LOD_THRESHOLD_MULTIPLIER); - _voxelColorAttribute->setUserFacing(true); - _voxelMaterialAttribute->setLODThresholdMultiplier(VOXEL_LOD_THRESHOLD_MULTIPLIER); - _voxelHermiteAttribute->setLODThresholdMultiplier(VOXEL_LOD_THRESHOLD_MULTIPLIER); } static QScriptValue qDebugFunction(QScriptContext* context, QScriptEngine* engine) { @@ -275,846 +262,6 @@ FloatAttribute::FloatAttribute(const QString& name) : SimpleInlineAttribute(name) { } -const float CHAR_SCALE = 127.0f; -const float INVERSE_CHAR_SCALE = 1.0f / CHAR_SCALE; - -QRgb packNormal(const glm::vec3& normal) { - return qRgb((char)(normal.x * CHAR_SCALE), (char)(normal.y * CHAR_SCALE), (char)(normal.z * CHAR_SCALE)); -} - -QRgb packNormal(const glm::vec3& normal, int alpha) { - return qRgba((char)(normal.x * CHAR_SCALE), (char)(normal.y * CHAR_SCALE), (char)(normal.z * CHAR_SCALE), alpha); -} - -glm::vec3 unpackNormal(QRgb value) { - return glm::vec3((char)qRed(value) * INVERSE_CHAR_SCALE, (char)qGreen(value) * INVERSE_CHAR_SCALE, - (char)qBlue(value) * INVERSE_CHAR_SCALE); -} - -DataBlock::~DataBlock() { -} - -MaterialObject::MaterialObject() : - _scaleS(1.0f), - _scaleT(1.0f) { -} - -static QHash countIndices(const QByteArray& contents) { - QHash counts; - for (const uchar* src = (const uchar*)contents.constData(), *end = src + contents.size(); src != end; src++) { - if (*src != 0) { - counts[*src]++; - } - } - return counts; -} - -const float EIGHT_BIT_MAXIMUM = 255.0f; - -uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, QByteArray& contents) { - if (!(material && static_cast(material.data())->getDiffuse().isValid())) { - return 0; - } - // first look for a matching existing material, noting the first reusable slot - int firstEmptyIndex = -1; - for (int i = 0; i < materials.size(); i++) { - const SharedObjectPointer& existingMaterial = materials.at(i); - if (existingMaterial) { - if (existingMaterial->equals(material.data())) { - return i + 1; - } - } else if (firstEmptyIndex == -1) { - firstEmptyIndex = i; - } - } - // if nothing found, use the first empty slot or append - if (firstEmptyIndex != -1) { - materials[firstEmptyIndex] = material; - return firstEmptyIndex + 1; - } - if (materials.size() < EIGHT_BIT_MAXIMUM) { - materials.append(material); - return materials.size(); - } - // last resort: find the least-used material and remove it - QHash counts = countIndices(contents); - uchar materialIndex = 0; - int lowestCount = INT_MAX; - for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { - if (it.value() < lowestCount) { - materialIndex = it.key(); - lowestCount = it.value(); - } - } - contents.replace((char)materialIndex, (char)0); - return materialIndex; -} - -void clearUnusedMaterials(QVector& materials, const QByteArray& contents) { - QHash counts = countIndices(contents); - for (int i = 0; i < materials.size(); i++) { - if (counts.value(i + 1) == 0) { - materials[i] = SharedObjectPointer(); - } - } - while (!(materials.isEmpty() || materials.last())) { - materials.removeLast(); - } -} - -const int VOXEL_COLOR_HEADER_SIZE = sizeof(qint32) * 6; - -static QByteArray encodeVoxelColor(int offsetX, int offsetY, int offsetZ, - int sizeX, int sizeY, int sizeZ, const QVector& contents) { - QByteArray inflated(VOXEL_COLOR_HEADER_SIZE, 0); - qint32* header = (qint32*)inflated.data(); - *header++ = offsetX; - *header++ = offsetY; - *header++ = offsetZ; - *header++ = sizeX; - *header++ = sizeY; - *header++ = sizeZ; - inflated.append((const char*)contents.constData(), contents.size() * sizeof(QRgb)); - return qCompress(inflated); -} - -static QVector decodeVoxelColor(const QByteArray& encoded, int& offsetX, int& offsetY, int& offsetZ, - int& sizeX, int& sizeY, int& sizeZ) { - QByteArray inflated = qUncompress(encoded); - const qint32* header = (const qint32*)inflated.constData(); - offsetX = *header++; - offsetY = *header++; - offsetZ = *header++; - sizeX = *header++; - sizeY = *header++; - sizeZ = *header++; - int payloadSize = inflated.size() - VOXEL_COLOR_HEADER_SIZE; - QVector contents(payloadSize / sizeof(QRgb)); - memcpy(contents.data(), inflated.constData() + VOXEL_COLOR_HEADER_SIZE, payloadSize); - return contents; -} - -VoxelColorData::VoxelColorData(const QVector& contents, int size) : - _contents(contents), - _size(size) { -} - -VoxelColorData::VoxelColorData(Bitstream& in, int bytes) { - read(in, bytes); -} - -VoxelColorData::VoxelColorData(Bitstream& in, int bytes, const VoxelColorDataPointer& reference) { - if (!reference) { - read(in, bytes); - return; - } - QMutexLocker locker(&reference->getEncodedDeltaMutex()); - reference->setEncodedDelta(in.readAligned(bytes)); - reference->setDeltaData(DataBlockPointer(this)); - _contents = reference->getContents(); - _size = reference->getSize(); - - int offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ; - QVector delta = decodeVoxelColor(reference->getEncodedDelta(), offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ); - if (delta.isEmpty()) { - return; - } - if (offsetX == 0) { - _contents = delta; - _size = sizeX; - return; - } - int minX = offsetX - 1; - int minY = offsetY - 1; - int minZ = offsetZ - 1; - const QRgb* src = delta.constData(); - int size2 = _size * _size; - QRgb* planeDest = _contents.data() + minZ * size2 + minY * _size + minX; - int length = sizeX * sizeof(QRgb); - for (int z = 0; z < sizeZ; z++, planeDest += size2) { - QRgb* dest = planeDest; - for (int y = 0; y < sizeY; y++, src += sizeX, dest += _size) { - memcpy(dest, src, length); - } - } -} - -void VoxelColorData::write(Bitstream& out) { - QMutexLocker locker(&_encodedMutex); - if (_encoded.isEmpty()) { - _encoded = encodeVoxelColor(0, 0, 0, _size, _size, _size, _contents); - } - out << _encoded.size(); - out.writeAligned(_encoded); -} - -void VoxelColorData::writeDelta(Bitstream& out, const VoxelColorDataPointer& reference) { - if (!reference || reference->getSize() != _size) { - write(out); - return; - } - QMutexLocker locker(&reference->getEncodedDeltaMutex()); - if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { - int minX = _size, minY = _size, minZ = _size; - int maxX = -1, maxY = -1, maxZ = -1; - const QRgb* src = _contents.constData(); - const QRgb* ref = reference->getContents().constData(); - for (int z = 0; z < _size; z++) { - bool differenceZ = false; - for (int y = 0; y < _size; y++) { - bool differenceY = false; - for (int x = 0; x < _size; x++) { - if (*src++ != *ref++) { - minX = qMin(minX, x); - maxX = qMax(maxX, x); - differenceY = differenceZ = true; - } - } - if (differenceY) { - minY = qMin(minY, y); - maxY = qMax(maxY, y); - } - } - if (differenceZ) { - minZ = qMin(minZ, z); - maxZ = qMax(maxZ, z); - } - } - QVector delta; - int sizeX = 0, sizeY = 0, sizeZ = 0; - if (maxX >= minX) { - sizeX = maxX - minX + 1; - sizeY = maxY - minY + 1; - sizeZ = maxZ - minZ + 1; - delta = QVector(sizeX * sizeY * sizeZ, 0); - QRgb* dest = delta.data(); - int size2 = _size * _size; - const QRgb* planeSrc = _contents.constData() + minZ * size2 + minY * _size + minX; - int length = sizeX * sizeof(QRgb); - for (int z = 0; z < sizeZ; z++, planeSrc += size2) { - src = planeSrc; - for (int y = 0; y < sizeY; y++, src += _size, dest += sizeX) { - memcpy(dest, src, length); - } - } - } - reference->setEncodedDelta(encodeVoxelColor(minX + 1, minY + 1, minZ + 1, sizeX, sizeY, sizeZ, delta)); - reference->setDeltaData(DataBlockPointer(this)); - } - out << reference->getEncodedDelta().size(); - out.writeAligned(reference->getEncodedDelta()); -} - -void VoxelColorData::read(Bitstream& in, int bytes) { - int offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ; - _contents = decodeVoxelColor(_encoded = in.readAligned(bytes), offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ); - _size = sizeX; -} - -VoxelColorAttribute::VoxelColorAttribute(const QString& name) : - InlineAttribute(name) { -} - -void VoxelColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - if (!isLeaf) { - return; - } - int size; - in >> size; - if (size == 0) { - *(VoxelColorDataPointer*)&value = VoxelColorDataPointer(); - } else { - *(VoxelColorDataPointer*)&value = VoxelColorDataPointer(new VoxelColorData(in, size)); - } -} - -void VoxelColorAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - if (!isLeaf) { - return; - } - VoxelColorDataPointer data = decodeInline(value); - if (data) { - data->write(out); - } else { - out << 0; - } -} - -void VoxelColorAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { - if (!isLeaf) { - return; - } - int size; - in >> size; - if (size == 0) { - *(VoxelColorDataPointer*)&value = VoxelColorDataPointer(); - } else { - *(VoxelColorDataPointer*)&value = VoxelColorDataPointer(new VoxelColorData( - in, size, decodeInline(reference))); - } -} - -void VoxelColorAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { - if (!isLeaf) { - return; - } - VoxelColorDataPointer data = decodeInline(value); - if (data) { - data->writeDelta(out, decodeInline(reference)); - } else { - out << 0; - } -} - -bool VoxelColorAttribute::merge(void*& parent, void* children[], bool postRead) const { - int maxSize = 0; - for (int i = 0; i < MERGE_COUNT; i++) { - VoxelColorDataPointer pointer = decodeInline(children[i]); - if (pointer) { - maxSize = qMax(maxSize, pointer->getSize()); - } - } - if (maxSize == 0) { - *(VoxelColorDataPointer*)&parent = VoxelColorDataPointer(); - return true; - } - int size = maxSize; - int area = size * size; - QVector contents(area * size); - int halfSize = size / 2; - int halfSizeComplement = size - halfSize; - for (int i = 0; i < MERGE_COUNT; i++) { - VoxelColorDataPointer child = decodeInline(children[i]); - if (!child) { - continue; - } - const QVector& childContents = child->getContents(); - int childSize = child->getSize(); - int childArea = childSize * childSize; - const int INDEX_MASK = 1; - int xIndex = i & INDEX_MASK; - const int Y_SHIFT = 1; - int yIndex = (i >> Y_SHIFT) & INDEX_MASK; - int Z_SHIFT = 2; - int zIndex = (i >> Z_SHIFT) & INDEX_MASK; - QRgb* dest = contents.data() + (zIndex * halfSize * area) + (yIndex * halfSize * size) + (xIndex * halfSize); - const QRgb* src = childContents.data(); - - const int MAX_ALPHA = 255; - if (childSize == size) { - // simple case: one destination value for four child values - for (int z = 0; z < halfSizeComplement; z++) { - int offset4 = (z == halfSize) ? 0 : childArea; - for (int y = 0; y < halfSizeComplement; y++) { - int offset2 = (y == halfSize) ? 0 : childSize; - int offset6 = offset4 + offset2; - for (QRgb* end = dest + halfSizeComplement; dest != end; ) { - int offset1 = (dest == end - 1) ? 0 : 1; - QRgb v0 = src[0], v1 = src[offset1], v2 = src[offset2], v3 = src[offset2 + offset1], v4 = src[offset4], - v5 = src[offset4 + offset1], v6 = src[offset6], v7 = src[offset6 + offset1]; - src += (1 + offset1); - int a0 = qAlpha(v0), a1 = qAlpha(v1), a2 = qAlpha(v2), a3 = qAlpha(v3), - a4 = qAlpha(v4), a5 = qAlpha(v5), a6 = qAlpha(v6), a7 = qAlpha(v7); - if (a0 == 0) { - *dest++ = qRgba(0, 0, 0, 0); - continue; - } - int alphaTotal = a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7; - *dest++ = qRgba( - (qRed(v0) * a0 + qRed(v1) * a1 + qRed(v2) * a2 + qRed(v3) * a3 + - qRed(v4) * a4 + qRed(v5) * a5 + qRed(v6) * a6 + qRed(v7) * a7) / alphaTotal, - (qGreen(v0) * a0 + qGreen(v1) * a1 + qGreen(v2) * a2 + qGreen(v3) * a3 + - qGreen(v4) * a4 + qGreen(v5) * a5 + qGreen(v6) * a6 + qGreen(v7) * a7) / alphaTotal, - (qBlue(v0) * a0 + qBlue(v1) * a1 + qBlue(v2) * a2 + qBlue(v3) * a3 + - qBlue(v4) * a4 + qBlue(v5) * a5 + qBlue(v6) * a6 + qBlue(v7) * a7) / alphaTotal, - MAX_ALPHA); - } - dest += halfSize; - src += offset2; - } - dest += halfSize * size; - src += offset4; - } - } else { - // more complex: N destination values for four child values - // ... - } - } - *(VoxelColorDataPointer*)&parent = VoxelColorDataPointer(new VoxelColorData(contents, size)); - return false; -} - -const int VOXEL_MATERIAL_HEADER_SIZE = sizeof(qint32) * 6; - -static QByteArray encodeVoxelMaterial(int offsetX, int offsetY, int offsetZ, - int sizeX, int sizeY, int sizeZ, const QByteArray& contents) { - QByteArray inflated(VOXEL_MATERIAL_HEADER_SIZE, 0); - qint32* header = (qint32*)inflated.data(); - *header++ = offsetX; - *header++ = offsetY; - *header++ = offsetZ; - *header++ = sizeX; - *header++ = sizeY; - *header++ = sizeZ; - inflated.append(contents); - return qCompress(inflated); -} - -static QByteArray decodeVoxelMaterial(const QByteArray& encoded, int& offsetX, int& offsetY, int& offsetZ, - int& sizeX, int& sizeY, int& sizeZ) { - QByteArray inflated = qUncompress(encoded); - const qint32* header = (const qint32*)inflated.constData(); - offsetX = *header++; - offsetY = *header++; - offsetZ = *header++; - sizeX = *header++; - sizeY = *header++; - sizeZ = *header++; - return inflated.mid(VOXEL_MATERIAL_HEADER_SIZE); -} - -VoxelMaterialData::VoxelMaterialData(const QByteArray& contents, int size, const QVector& materials) : - _contents(contents), - _size(size), - _materials(materials) { -} - -VoxelMaterialData::VoxelMaterialData(Bitstream& in, int bytes) { - read(in, bytes); -} - -VoxelMaterialData::VoxelMaterialData(Bitstream& in, int bytes, const VoxelMaterialDataPointer& reference) { - if (!reference) { - read(in, bytes); - return; - } - QMutexLocker locker(&reference->getEncodedDeltaMutex()); - reference->setEncodedDelta(in.readAligned(bytes)); - in.readDelta(_materials, reference->getMaterials()); - reference->setDeltaData(DataBlockPointer(this)); - _contents = reference->getContents(); - _size = reference->getSize(); - - int offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ; - QByteArray delta = decodeVoxelMaterial(reference->getEncodedDelta(), offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ); - if (delta.isEmpty()) { - return; - } - if (offsetX == 0) { - _contents = delta; - _size = sizeX; - return; - } - int minX = offsetX - 1; - int minY = offsetY - 1; - int minZ = offsetZ - 1; - const char* src = delta.constData(); - int size2 = _size * _size; - char* planeDest = _contents.data() + minZ * size2 + minY * _size + minX; - for (int z = 0; z < sizeZ; z++, planeDest += size2) { - char* dest = planeDest; - for (int y = 0; y < sizeY; y++, src += sizeX, dest += _size) { - memcpy(dest, src, sizeX); - } - } -} - -void VoxelMaterialData::write(Bitstream& out) { - QMutexLocker locker(&_encodedMutex); - if (_encoded.isEmpty()) { - _encoded = encodeVoxelMaterial(0, 0, 0, _size, _size, _size, _contents); - } - out << _encoded.size(); - out.writeAligned(_encoded); - out << _materials; -} - -void VoxelMaterialData::writeDelta(Bitstream& out, const VoxelMaterialDataPointer& reference) { - if (!reference || reference->getSize() != _size) { - write(out); - return; - } - QMutexLocker locker(&reference->getEncodedDeltaMutex()); - if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { - int minX = _size, minY = _size, minZ = _size; - int maxX = -1, maxY = -1, maxZ = -1; - const char* src = _contents.constData(); - const char* ref = reference->getContents().constData(); - for (int z = 0; z < _size; z++) { - bool differenceZ = false; - for (int y = 0; y < _size; y++) { - bool differenceY = false; - for (int x = 0; x < _size; x++) { - if (*src++ != *ref++) { - minX = qMin(minX, x); - maxX = qMax(maxX, x); - differenceY = differenceZ = true; - } - } - if (differenceY) { - minY = qMin(minY, y); - maxY = qMax(maxY, y); - } - } - if (differenceZ) { - minZ = qMin(minZ, z); - maxZ = qMax(maxZ, z); - } - } - QByteArray delta; - int sizeX = 0, sizeY = 0, sizeZ = 0; - if (maxX >= minX) { - sizeX = maxX - minX + 1; - sizeY = maxY - minY + 1; - sizeZ = maxZ - minZ + 1; - delta = QByteArray(sizeX * sizeY * sizeZ, 0); - char* dest = delta.data(); - int size2 = _size * _size; - const char* planeSrc = _contents.constData() + minZ * size2 + minY * _size + minX; - for (int z = 0; z < sizeZ; z++, planeSrc += size2) { - src = planeSrc; - for (int y = 0; y < sizeY; y++, src += _size, dest += sizeX) { - memcpy(dest, src, sizeX); - } - } - } - reference->setEncodedDelta(encodeVoxelMaterial(minX + 1, minY + 1, minZ + 1, sizeX, sizeY, sizeZ, delta)); - reference->setDeltaData(DataBlockPointer(this)); - } - out << reference->getEncodedDelta().size(); - out.writeAligned(reference->getEncodedDelta()); - out.writeDelta(_materials, reference->getMaterials()); -} - -void VoxelMaterialData::read(Bitstream& in, int bytes) { - int offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ; - _contents = decodeVoxelMaterial(_encoded = in.readAligned(bytes), offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ); - _size = sizeX; - in >> _materials; -} - -VoxelMaterialAttribute::VoxelMaterialAttribute(const QString& name) : - InlineAttribute(name) { -} - -void VoxelMaterialAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - if (!isLeaf) { - return; - } - int size; - in >> size; - if (size == 0) { - *(VoxelMaterialDataPointer*)&value = VoxelMaterialDataPointer(); - } else { - *(VoxelMaterialDataPointer*)&value = VoxelMaterialDataPointer(new VoxelMaterialData(in, size)); - } -} - -void VoxelMaterialAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - if (!isLeaf) { - return; - } - VoxelMaterialDataPointer data = decodeInline(value); - if (data) { - data->write(out); - } else { - out << 0; - } -} - -void VoxelMaterialAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { - if (!isLeaf) { - return; - } - int size; - in >> size; - if (size == 0) { - *(VoxelMaterialDataPointer*)&value = VoxelMaterialDataPointer(); - } else { - *(VoxelMaterialDataPointer*)&value = VoxelMaterialDataPointer(new VoxelMaterialData( - in, size, decodeInline(reference))); - } -} - -void VoxelMaterialAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { - if (!isLeaf) { - return; - } - VoxelMaterialDataPointer data = decodeInline(value); - if (data) { - data->writeDelta(out, decodeInline(reference)); - } else { - out << 0; - } -} - -bool VoxelMaterialAttribute::merge(void*& parent, void* children[], bool postRead) const { - int maxSize = 0; - for (int i = 0; i < MERGE_COUNT; i++) { - VoxelMaterialDataPointer pointer = decodeInline(children[i]); - if (pointer) { - maxSize = qMax(maxSize, pointer->getSize()); - } - } - *(VoxelMaterialDataPointer*)&parent = VoxelMaterialDataPointer(); - return maxSize == 0; -} - -VoxelHermiteData::VoxelHermiteData(const QVector& contents, int size) : - _contents(contents), - _size(size) { -} - -VoxelHermiteData::VoxelHermiteData(Bitstream& in, int bytes) { - read(in, bytes); -} - -VoxelHermiteData::VoxelHermiteData(Bitstream& in, int bytes, const VoxelHermiteDataPointer& reference) { - if (!reference) { - read(in, bytes); - return; - } - QMutexLocker locker(&reference->getEncodedDeltaMutex()); - reference->setEncodedDelta(in.readAligned(bytes)); - reference->setDeltaData(DataBlockPointer(this)); - _contents = reference->getContents(); - _size = reference->getSize(); - - int offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ; - QVector delta = decodeVoxelColor(reference->getEncodedDelta(), offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ); - if (delta.isEmpty()) { - return; - } - if (offsetX == 0) { - _contents = delta; - _size = sizeX; - return; - } - int minX = offsetX - 1; - int minY = offsetY - 1; - int minZ = offsetZ - 1; - const QRgb* src = delta.constData(); - int destStride = _size * EDGE_COUNT; - int destStride2 = _size * destStride; - QRgb* planeDest = _contents.data() + minZ * destStride2 + minY * destStride + minX * EDGE_COUNT; - int srcStride = sizeX * EDGE_COUNT; - int length = srcStride * sizeof(QRgb); - for (int z = 0; z < sizeZ; z++, planeDest += destStride2) { - QRgb* dest = planeDest; - for (int y = 0; y < sizeY; y++, src += srcStride, dest += destStride) { - memcpy(dest, src, length); - } - } -} - -void VoxelHermiteData::write(Bitstream& out) { - QMutexLocker locker(&_encodedMutex); - if (_encoded.isEmpty()) { - _encoded = encodeVoxelColor(0, 0, 0, _size, _size, _size, _contents); - } - out << _encoded.size(); - out.writeAligned(_encoded); -} - -void VoxelHermiteData::writeDelta(Bitstream& out, const VoxelHermiteDataPointer& reference) { - if (!reference || reference->getSize() != _size) { - write(out); - return; - } - QMutexLocker locker(&reference->getEncodedDeltaMutex()); - if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { - int minX = _size, minY = _size, minZ = _size; - int maxX = -1, maxY = -1, maxZ = -1; - const QRgb* src = _contents.constData(); - const QRgb* ref = reference->getContents().constData(); - for (int z = 0; z < _size; z++) { - bool differenceZ = false; - for (int y = 0; y < _size; y++) { - bool differenceY = false; - for (int x = 0; x < _size; x++, src += EDGE_COUNT, ref += EDGE_COUNT) { - if (src[0] != ref[0] || src[1] != ref[1] || src[2] != ref[2]) { - minX = qMin(minX, x); - maxX = qMax(maxX, x); - differenceY = differenceZ = true; - } - } - if (differenceY) { - minY = qMin(minY, y); - maxY = qMax(maxY, y); - } - } - if (differenceZ) { - minZ = qMin(minZ, z); - maxZ = qMax(maxZ, z); - } - } - QVector delta; - int sizeX = 0, sizeY = 0, sizeZ = 0; - if (maxX >= minX) { - sizeX = maxX - minX + 1; - sizeY = maxY - minY + 1; - sizeZ = maxZ - minZ + 1; - delta = QVector(sizeX * sizeY * sizeZ * EDGE_COUNT, 0); - QRgb* dest = delta.data(); - int srcStride = _size * EDGE_COUNT; - int srcStride2 = _size * srcStride; - const QRgb* planeSrc = _contents.constData() + minZ * srcStride2 + minY * srcStride + minX * EDGE_COUNT; - int destStride = sizeX * EDGE_COUNT; - int length = destStride * sizeof(QRgb); - for (int z = 0; z < sizeZ; z++, planeSrc += srcStride2) { - src = planeSrc; - for (int y = 0; y < sizeY; y++, src += srcStride, dest += destStride) { - memcpy(dest, src, length); - } - } - } - reference->setEncodedDelta(encodeVoxelColor(minX + 1, minY + 1, minZ + 1, sizeX, sizeY, sizeZ, delta)); - reference->setDeltaData(DataBlockPointer(this)); - } - out << reference->getEncodedDelta().size(); - out.writeAligned(reference->getEncodedDelta()); -} - -void VoxelHermiteData::read(Bitstream& in, int bytes) { - int offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ; - _contents = decodeVoxelColor(_encoded = in.readAligned(bytes), offsetX, offsetY, offsetZ, sizeX, sizeY, sizeZ); - _size = sizeX; -} - -VoxelHermiteAttribute::VoxelHermiteAttribute(const QString& name) : - InlineAttribute(name) { -} - -void VoxelHermiteAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - if (!isLeaf) { - return; - } - int size; - in >> size; - if (size == 0) { - *(VoxelHermiteDataPointer*)&value = VoxelHermiteDataPointer(); - } else { - *(VoxelHermiteDataPointer*)&value = VoxelHermiteDataPointer(new VoxelHermiteData(in, size)); - } -} - -void VoxelHermiteAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - if (!isLeaf) { - return; - } - VoxelHermiteDataPointer data = decodeInline(value); - if (data) { - data->write(out); - } else { - out << 0; - } -} - -void VoxelHermiteAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { - if (!isLeaf) { - return; - } - int size; - in >> size; - if (size == 0) { - *(VoxelHermiteDataPointer*)&value = VoxelHermiteDataPointer(); - } else { - *(VoxelHermiteDataPointer*)&value = VoxelHermiteDataPointer(new VoxelHermiteData( - in, size, decodeInline(reference))); - } -} - -void VoxelHermiteAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { - if (!isLeaf) { - return; - } - VoxelHermiteDataPointer data = decodeInline(value); - if (data) { - data->writeDelta(out, decodeInline(reference)); - } else { - out << 0; - } -} - -bool VoxelHermiteAttribute::merge(void*& parent, void* children[], bool postRead) const { - int maxSize = 0; - for (int i = 0; i < MERGE_COUNT; i++) { - VoxelHermiteDataPointer pointer = decodeInline(children[i]); - if (pointer) { - maxSize = qMax(maxSize, pointer->getSize()); - } - } - if (maxSize == 0) { - *(VoxelHermiteDataPointer*)&parent = VoxelHermiteDataPointer(); - return true; - } - int size = maxSize; - int area = size * size; - QVector contents(area * size * VoxelHermiteData::EDGE_COUNT); - int halfSize = size / 2; - int halfSizeComplement = size - halfSize; - for (int i = 0; i < MERGE_COUNT; i++) { - VoxelHermiteDataPointer child = decodeInline(children[i]); - if (!child) { - continue; - } - const QVector& childContents = child->getContents(); - int childSize = child->getSize(); - int childArea = childSize * childSize; - const int INDEX_MASK = 1; - int xIndex = i & INDEX_MASK; - const int Y_SHIFT = 1; - int yIndex = (i >> Y_SHIFT) & INDEX_MASK; - int Z_SHIFT = 2; - int zIndex = (i >> Z_SHIFT) & INDEX_MASK; - QRgb* dest = contents.data() + ((zIndex * halfSize * area) + (yIndex * halfSize * size) + (xIndex * halfSize)) * - VoxelHermiteData::EDGE_COUNT; - const QRgb* src = childContents.data(); - int offsets[VoxelHermiteData::EDGE_COUNT]; - - if (childSize == size) { - // simple case: one destination value for four child values - for (int z = 0; z < halfSizeComplement; z++) { - offsets[2] = (z == halfSize) ? 0 : (childArea * VoxelHermiteData::EDGE_COUNT); - for (int y = 0; y < halfSizeComplement; y++) { - offsets[1] = (y == halfSize) ? 0 : (childSize * VoxelHermiteData::EDGE_COUNT); - for (QRgb* end = dest + halfSizeComplement * VoxelHermiteData::EDGE_COUNT; dest != end; - dest += VoxelHermiteData::EDGE_COUNT) { - offsets[0] = (dest == end - VoxelHermiteData::EDGE_COUNT) ? 0 : VoxelHermiteData::EDGE_COUNT; - for (int i = 0; i < VoxelHermiteData::EDGE_COUNT; i++) { - QRgb v0 = src[i], v1 = src[i + offsets[i]]; - glm::vec3 n0 = unpackNormal(v0), n1 = unpackNormal(v1); - float l0 = glm::length(n0), l1 = glm::length(n1); - float lengthTotal = l0 + l1; - if (lengthTotal == 0.0f) { - dest[i] = qRgba(0, 0, 0, 0); - continue; - } - glm::vec3 combinedNormal = n0 + n1; - float combinedLength = glm::length(combinedNormal); - if (combinedLength > 0.0f) { - combinedNormal /= combinedLength; - } - float combinedOffset = qAlpha(v0) * 0.5f * l0 + (qAlpha(v1) + EIGHT_BIT_MAXIMUM) * 0.5f * l1; - dest[i] = packNormal(combinedNormal, combinedOffset / lengthTotal); - } - src += (VoxelHermiteData::EDGE_COUNT + offsets[0]); - } - dest += (halfSize * VoxelHermiteData::EDGE_COUNT); - src += offsets[1]; - } - dest += (halfSize * size * VoxelHermiteData::EDGE_COUNT); - src += offsets[2]; - } - } else { - // more complex: N destination values for four child values - // ... - } - } - *(VoxelHermiteDataPointer*)&parent = VoxelHermiteDataPointer(new VoxelHermiteData(contents, size)); - return false; -} - SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject, const SharedObjectPointer& defaultValue) : InlineAttribute(name, defaultValue), diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 50814ac912..ea0ec263e4 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -30,16 +30,10 @@ class QScriptValue; class Attribute; class DataBlock; -class HeightfieldColorData; -class HeightfieldHeightData; -class HeightfieldMaterialData; class MetavoxelData; class MetavoxelLOD; class MetavoxelNode; class MetavoxelStreamState; -class VoxelColorData; -class VoxelHermiteData; -class VoxelMaterialData; typedef SharedObjectPointerTemplate AttributePointer; @@ -88,24 +82,6 @@ public: /// Returns a reference to the standard SharedObjectSet "spanners" attribute. const AttributePointer& getSpannersAttribute() const { return _spannersAttribute; } - /// Returns a reference to the standard HeightfieldHeightDataPointer "heightfield" attribute. - const AttributePointer& getHeightfieldAttribute() const { return _heightfieldAttribute; } - - /// Returns a reference to the standard HeightfieldColorDataPointer "heightfieldColor" attribute. - const AttributePointer& getHeightfieldColorAttribute() const { return _heightfieldColorAttribute; } - - /// Returns a reference to the standard HeightfieldMaterialDataPointer "heightfieldMaterial" attribute. - const AttributePointer& getHeightfieldMaterialAttribute() const { return _heightfieldMaterialAttribute; } - - /// Returns a reference to the standard VoxelColorDataPointer "voxelColor" attribute. - const AttributePointer& getVoxelColorAttribute() const { return _voxelColorAttribute; } - - /// Returns a reference to the standard VoxelMaterialDataPointer "voxelMaterial" attribute. - const AttributePointer& getVoxelMaterialAttribute() const { return _voxelMaterialAttribute; } - - /// Returns a reference to the standard VoxelHermiteDataPointer "voxelHermite" attribute. - const AttributePointer& getVoxelHermiteAttribute() const { return _voxelHermiteAttribute; } - private: static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine); @@ -116,12 +92,6 @@ private: AttributePointer _guideAttribute; AttributePointer _rendererAttribute; AttributePointer _spannersAttribute; - AttributePointer _heightfieldAttribute; - AttributePointer _heightfieldColorAttribute; - AttributePointer _heightfieldMaterialAttribute; - AttributePointer _voxelColorAttribute; - AttributePointer _voxelMaterialAttribute; - AttributePointer _voxelHermiteAttribute; }; /// Converts a value to a void pointer. @@ -355,213 +325,6 @@ public: Q_INVOKABLE FloatAttribute(const QString& name = QString()); }; -/// Packs a normal into an RGB value. -QRgb packNormal(const glm::vec3& normal); - -/// Packs a normal (plus extra alpha value) into an RGBA value. -QRgb packNormal(const glm::vec3& normal, int alpha); - -/// Unpacks a normal from an RGB value. -glm::vec3 unpackNormal(QRgb value); - -typedef QExplicitlySharedDataPointer DataBlockPointer; - -/// Base class for blocks of data. -class DataBlock : public QSharedData { -public: - - static const int COLOR_BYTES = 3; - - virtual ~DataBlock(); - - void setDeltaData(const DataBlockPointer& deltaData) { _deltaData = deltaData; } - const DataBlockPointer& getDeltaData() const { return _deltaData; } - - void setEncodedDelta(const QByteArray& encodedDelta) { _encodedDelta = encodedDelta; } - const QByteArray& getEncodedDelta() const { return _encodedDelta; } - - QMutex& getEncodedDeltaMutex() { return _encodedDeltaMutex; } - -protected: - - QByteArray _encoded; - QMutex _encodedMutex; - - DataBlockPointer _deltaData; - QByteArray _encodedDelta; - QMutex _encodedDeltaMutex; - - class EncodedSubdivision { - public: - DataBlockPointer ancestor; - QByteArray data; - }; - QVector _encodedSubdivisions; - QMutex _encodedSubdivisionsMutex; -}; - -/// Contains the description of a material. -class MaterialObject : public SharedObject { - Q_OBJECT - Q_PROPERTY(QUrl diffuse MEMBER _diffuse) - Q_PROPERTY(float scaleS MEMBER _scaleS) - Q_PROPERTY(float scaleT MEMBER _scaleT) - -public: - - Q_INVOKABLE MaterialObject(); - - const QUrl& getDiffuse() const { return _diffuse; } - - float getScaleS() const { return _scaleS; } - float getScaleT() const { return _scaleT; } - -private: - - QUrl _diffuse; - float _scaleS; - float _scaleT; -}; - -/// Utility method for editing: given a material pointer and a list of materials, returns the corresponding material index, -/// creating a new entry in the list if necessary. -uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, QByteArray& contents); - -/// Utility method for editing: removes any unused materials from the supplied list. -void clearUnusedMaterials(QVector& materials, const QByteArray& contents); - -typedef QExplicitlySharedDataPointer VoxelColorDataPointer; - -/// Contains a block of voxel color data. -class VoxelColorData : public DataBlock { -public: - - VoxelColorData(const QVector& contents, int size); - VoxelColorData(Bitstream& in, int bytes); - VoxelColorData(Bitstream& in, int bytes, const VoxelColorDataPointer& reference); - - const QVector& getContents() const { return _contents; } - - int getSize() const { return _size; } - - void write(Bitstream& out); - void writeDelta(Bitstream& out, const VoxelColorDataPointer& reference); - -private: - - void read(Bitstream& in, int bytes); - - QVector _contents; - int _size; -}; - -/// An attribute that stores voxel colors. -class VoxelColorAttribute : public InlineAttribute { - Q_OBJECT - -public: - - Q_INVOKABLE VoxelColorAttribute(const QString& name = QString()); - - 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; -}; - -typedef QExplicitlySharedDataPointer VoxelMaterialDataPointer; - -/// Contains a block of voxel material data. -class VoxelMaterialData : public DataBlock { -public: - - VoxelMaterialData(const QByteArray& contents, int size, - const QVector& materials = QVector()); - VoxelMaterialData(Bitstream& in, int bytes); - VoxelMaterialData(Bitstream& in, int bytes, const VoxelMaterialDataPointer& reference); - - const QByteArray& getContents() const { return _contents; } - - int getSize() const { return _size; } - - const QVector& getMaterials() const { return _materials; } - - void write(Bitstream& out); - void writeDelta(Bitstream& out, const VoxelMaterialDataPointer& reference); - -private: - - void read(Bitstream& in, int bytes); - - QByteArray _contents; - int _size; - QVector _materials; -}; - -/// An attribute that stores voxel materials. -class VoxelMaterialAttribute : public InlineAttribute { - Q_OBJECT - -public: - - Q_INVOKABLE VoxelMaterialAttribute(const QString& name = QString()); - - 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; -}; - -typedef QExplicitlySharedDataPointer VoxelHermiteDataPointer; - -/// Contains a block of voxel Hermite data (positions and normals at edge crossings). -class VoxelHermiteData : public DataBlock { -public: - - static const int EDGE_COUNT = 3; - - VoxelHermiteData(const QVector& contents, int size); - VoxelHermiteData(Bitstream& in, int bytes); - VoxelHermiteData(Bitstream& in, int bytes, const VoxelHermiteDataPointer& reference); - - const QVector& getContents() const { return _contents; } - - int getSize() const { return _size; } - - void write(Bitstream& out); - void writeDelta(Bitstream& out, const VoxelHermiteDataPointer& reference); - -private: - - void read(Bitstream& in, int bytes); - - QVector _contents; - int _size; -}; - -/// An attribute that stores voxel Hermite data. -class VoxelHermiteAttribute : public InlineAttribute { - Q_OBJECT - -public: - - Q_INVOKABLE VoxelHermiteAttribute(const QString& name = QString()); - - 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; -}; - /// An attribute that takes the form of QObjects of a given meta-type (a subclass of SharedObject). class SharedObjectAttribute : public InlineAttribute { Q_OBJECT diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index e236ffd217..ae4a96ad4f 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -715,8 +715,9 @@ void ReliableChannel::endMessage() { quint32 length = _buffer.pos() - _messageLengthPlaceholder; _buffer.writeBytes(_messageLengthPlaceholder, sizeof(quint32), (const char*)&length); - _messageReceivedOffset = getBytesWritten(); - _messageSize = length; + + pruneOutgoingMessageStats(); + _outgoingMessageStats.append(OffsetSizePair(getBytesWritten(), length)); } void ReliableChannel::sendMessage(const QVariant& message) { @@ -725,12 +726,14 @@ void ReliableChannel::sendMessage(const QVariant& message) { endMessage(); } -bool ReliableChannel::getMessageSendProgress(int& sent, int& total) const { - if (!_messagesEnabled || _offset >= _messageReceivedOffset) { +bool ReliableChannel::getMessageSendProgress(int& sent, int& total) { + pruneOutgoingMessageStats(); + if (!_messagesEnabled || _outgoingMessageStats.isEmpty()) { return false; } - sent = qMax(0, _messageSize - (_messageReceivedOffset - _offset)); - total = _messageSize; + const OffsetSizePair& stat = _outgoingMessageStats.first(); + sent = qMax(0, stat.second - (stat.first - _offset)); + total = stat.second; return true; } @@ -770,8 +773,7 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o _offset(0), _writePosition(0), _writePositionResetPacketNumber(0), - _messagesEnabled(true), - _messageReceivedOffset(0) { + _messagesEnabled(true) { _buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly); _dataStream.setByteOrder(QDataStream::LittleEndian); @@ -942,3 +944,9 @@ void ReliableChannel::readData(QDataStream& in) { } } +void ReliableChannel::pruneOutgoingMessageStats() { + while (!_outgoingMessageStats.isEmpty() && _offset >= _outgoingMessageStats.first().first) { + _outgoingMessageStats.removeFirst(); + } +} + diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index d3d37f2621..739373d137 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -386,7 +386,7 @@ public: /// Determines the number of bytes uploaded towards the currently pending message. /// \return true if there is a message pending, in which case the sent and total arguments will be set - bool getMessageSendProgress(int& sent, int& total) const; + bool getMessageSendProgress(int& sent, int& total); /// Determines the number of bytes downloaded towards the currently pending message. /// \return true if there is a message pending, in which case the received and total arguments will be set @@ -418,6 +418,8 @@ private: void readData(QDataStream& in); + void pruneOutgoingMessageStats(); + int _index; bool _output; CircularBuffer _buffer; @@ -432,6 +434,10 @@ private: SpanList _acknowledged; bool _messagesEnabled; int _messageLengthPlaceholder; ///< the location in the buffer of the message length for the current message + + typedef QPair OffsetSizePair; + QVector _outgoingMessageStats; + int _messageReceivedOffset; ///< when reached, indicates that the most recent sent message has been received int _messageSize; ///< the size of the most recent sent message; only valid when _messageReceivedOffset has been set }; diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 860ab3e5e9..b8b03226d2 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -29,7 +29,9 @@ MetavoxelLOD::MetavoxelLOD(const glm::vec3& position, float threshold) : } bool MetavoxelLOD::shouldSubdivide(const glm::vec3& minimum, float size, float multiplier) const { - return size >= glm::distance(position, minimum + glm::vec3(size, size, size) * 0.5f) * threshold * multiplier; + float halfSize = size * 0.5f; + return size >= (glm::distance(position, minimum + glm::vec3(halfSize, halfSize, halfSize)) - halfSize) * + threshold * multiplier; } bool MetavoxelLOD::becameSubdivided(const glm::vec3& minimum, float size, @@ -57,7 +59,9 @@ bool MetavoxelLOD::becameSubdividedOrCollapsed(const glm::vec3& minimum, float s } bool MetavoxelLOD::shouldSubdivide(const glm::vec2& minimum, float size, float multiplier) const { - return size >= glm::distance(glm::vec2(position), minimum + glm::vec2(size, size) * 0.5f) * threshold * multiplier; + float halfSize = size * 0.5f; + return size >= (glm::distance(glm::vec2(position), minimum + glm::vec2(halfSize, halfSize)) - halfSize) * + threshold * multiplier; } bool MetavoxelLOD::becameSubdivided(const glm::vec2& minimum, float size, @@ -1188,67 +1192,6 @@ void MetavoxelNode::writeSpanners(MetavoxelStreamState& state) const { } } -void MetavoxelNode::writeSpannerDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const { - SharedObjectSet oldSet = decodeInline(reference.getAttributeValue()); - SharedObjectSet newSet = decodeInline(_attributeValue); - foreach (const SharedObjectPointer& object, oldSet) { - if (static_cast(object.data())->testAndSetVisited(state.base.visit) && !newSet.contains(object)) { - state.base.stream << object; - } - } - foreach (const SharedObjectPointer& object, newSet) { - if (static_cast(object.data())->testAndSetVisited(state.base.visit) && !oldSet.contains(object)) { - state.base.stream << object; - } - } - if (isLeaf() || !state.shouldSubdivide()) { - if (!reference.isLeaf() && state.shouldSubdivideReference()) { - MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - reference._children[i]->writeSpanners(nextState); - } - } - return; - } - MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; - if (reference.isLeaf() || !state.shouldSubdivideReference()) { - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i]->writeSpanners(nextState); - } - return; - } - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - if (_children[i] != reference._children[i]) { - _children[i]->writeSpannerDelta(*reference._children[i], nextState); - - } else if (nextState.becameSubdivided()) { - _children[i]->writeSpannerSubdivision(nextState); - } - } -} - -void MetavoxelNode::writeSpannerSubdivision(MetavoxelStreamState& state) const { - if (!isLeaf()) { - MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; - if (!state.shouldSubdivideReference()) { - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i]->writeSpanners(nextState); - } - } else { - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - if (nextState.becameSubdivided()) { - _children[i]->writeSpannerSubdivision(nextState); - } - } - } - } -} - void MetavoxelNode::decrementReferenceCount(const AttributePointer& attribute) { if (!_referenceCount.deref()) { destroy(attribute); @@ -1621,8 +1564,9 @@ static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, int e bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { // save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute - visitation.info.lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) * - visitation.visitor->getLOD().threshold; + float halfSize = visitation.info.size * 0.5f; + visitation.info.lodBase = (glm::distance(visitation.visitor->getLOD().position, visitation.info.minimum + + glm::vec3(halfSize, halfSize, halfSize)) - halfSize) * visitation.visitor->getLOD().threshold; visitation.info.isLODLeaf = (visitation.info.size < visitation.info.lodBase * visitation.visitor->getMinimumLODThresholdMultiplier()); visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves(); @@ -1651,8 +1595,9 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) { // save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute - visitation.info.lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) * - visitation.visitor->getLOD().threshold; + float halfSize = visitation.info.size * 0.5f; + visitation.info.lodBase = (glm::distance(visitation.visitor->getLOD().position, visitation.info.minimum + + glm::vec3(halfSize, halfSize, halfSize)) - halfSize) * visitation.visitor->getLOD().threshold; visitation.info.isLODLeaf = (visitation.info.size < visitation.info.lodBase * visitation.visitor->getMinimumLODThresholdMultiplier()); visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves(); diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 56d9dd3a8a..806c5bf2ae 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -239,9 +239,7 @@ public: void writeSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, void* ancestorValue) const; void writeSpanners(MetavoxelStreamState& state) const; - void writeSpannerDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const; - void writeSpannerSubdivision(MetavoxelStreamState& state) const; - + /// Increments the node's reference count. void incrementReferenceCount() { _referenceCount.ref(); } diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 1225752df7..bae5768068 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "MetavoxelMessages.h" #include "Spanner.h" @@ -146,10 +148,13 @@ void SetDataEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects data.set(minimum, this->data, blend); } -PaintHeightfieldHeightEdit::PaintHeightfieldHeightEdit(const glm::vec3& position, float radius, float height) : +PaintHeightfieldHeightEdit::PaintHeightfieldHeightEdit(const glm::vec3& position, float radius, + float height, bool set, bool erase) : position(position), radius(radius), - height(height) { + height(height), + set(set), + erase(erase) { } void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { @@ -161,7 +166,7 @@ void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObje Box(position - extents, position + extents), results); foreach (const SharedObjectPointer& spanner, results) { - Spanner* newSpanner = static_cast(spanner.data())->paintHeight(position, radius, height); + Spanner* newSpanner = static_cast(spanner.data())->paintHeight(position, radius, height, set, erase); if (newSpanner != spanner) { data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner); } @@ -173,473 +178,100 @@ MaterialEdit::MaterialEdit(const SharedObjectPointer& material, const QColor& av averageColor(averageColor) { } -PaintHeightfieldMaterialEdit::PaintHeightfieldMaterialEdit(const glm::vec3& position, float radius, - const SharedObjectPointer& material, const QColor& averageColor) : +HeightfieldMaterialSpannerEdit::HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner, + const SharedObjectPointer& material, const QColor& averageColor, bool paint, bool voxelize) : MaterialEdit(material, averageColor), + spanner(spanner), + paint(paint), + voxelize(voxelize) { +} + +class SpannerProjectionFetchVisitor : public SpannerVisitor { +public: + + SpannerProjectionFetchVisitor(const Box& bounds, QVector& results); + + virtual bool visit(Spanner* spanner); + +private: + + const Box& _bounds; + QVector& _results; + float _closestDistance; +}; + +SpannerProjectionFetchVisitor::SpannerProjectionFetchVisitor(const Box& bounds, QVector& results) : + SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute()), + _bounds(bounds), + _results(results), + _closestDistance(FLT_MAX) { +} + +bool SpannerProjectionFetchVisitor::visit(Spanner* spanner) { + Heightfield* heightfield = qobject_cast(spanner); + if (!heightfield) { + return true; + } + glm::mat4 transform = glm::scale(1.0f / glm::vec3(heightfield->getScale(), + heightfield->getScale() * heightfield->getAspectY(), + heightfield->getScale() * heightfield->getAspectZ())) * + glm::mat4_cast(glm::inverse(heightfield->getRotation())) * glm::translate(-heightfield->getTranslation()); + Box transformedBounds = transform * _bounds; + if (transformedBounds.maximum.x < 0.0f || transformedBounds.maximum.z < 0.0f || + transformedBounds.minimum.x > 1.0f || transformedBounds.minimum.z > 1.0f) { + return true; + } + float distance = qMin(glm::abs(transformedBounds.minimum.y), glm::abs(transformedBounds.maximum.y)); + if (distance < _closestDistance) { + _results.clear(); + _results.append(spanner); + _closestDistance = distance; + } + return true; +} + +void HeightfieldMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + // make sure the color meets our transparency requirements + QColor color = averageColor; + if (paint) { + color.setAlphaF(1.0f); + + } else if (color.alphaF() < 0.5f) { + color = QColor(0, 0, 0, 0); + } + QVector results; + data.getIntersecting(AttributeRegistry::getInstance()->getSpannersAttribute(), + static_cast(spanner.data())->getBounds(), results); + + // if there's nothing intersecting directly, find the closest heightfield that intersects the projection + if (results.isEmpty()) { + SpannerProjectionFetchVisitor visitor(static_cast(spanner.data())->getBounds(), results); + data.guide(visitor); + } + + foreach (const SharedObjectPointer& result, results) { + Spanner* newResult = static_cast(result.data())->setMaterial(spanner, material, color, paint, voxelize); + if (newResult != result) { + data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), result, newResult); + } + } +} + +FillHeightfieldHeightEdit::FillHeightfieldHeightEdit(const glm::vec3& position, float radius) : position(position), radius(radius) { } -void PaintHeightfieldMaterialEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { - glm::vec3 extents(radius, radius, radius); +void FillHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + glm::vec3 extents = glm::vec3(radius, radius, radius); QVector results; data.getIntersecting(AttributeRegistry::getInstance()->getSpannersAttribute(), Box(position - extents, position + extents), results); foreach (const SharedObjectPointer& spanner, results) { - Spanner* newSpanner = static_cast(spanner.data())->paintMaterial(position, radius, material, averageColor); + Spanner* newSpanner = static_cast(spanner.data())->fillHeight(position, radius); if (newSpanner != spanner) { data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner); } } } - -const int VOXEL_BLOCK_SIZE = 16; -const int VOXEL_BLOCK_SAMPLES = VOXEL_BLOCK_SIZE + 1; -const int VOXEL_BLOCK_AREA = VOXEL_BLOCK_SAMPLES * VOXEL_BLOCK_SAMPLES; -const int VOXEL_BLOCK_VOLUME = VOXEL_BLOCK_AREA * VOXEL_BLOCK_SAMPLES; - -VoxelMaterialSpannerEdit::VoxelMaterialSpannerEdit(const SharedObjectPointer& spanner, - const SharedObjectPointer& material, const QColor& averageColor) : - MaterialEdit(material, averageColor), - spanner(spanner) { -} - -class VoxelMaterialSpannerEditVisitor : public MetavoxelVisitor { -public: - - VoxelMaterialSpannerEditVisitor(Spanner* spanner, const SharedObjectPointer& material, const QColor& color); - - virtual int visit(MetavoxelInfo& info); - -private: - - Spanner* _spanner; - SharedObjectPointer _material; - QColor _color; - float _blockSize; -}; - -VoxelMaterialSpannerEditVisitor::VoxelMaterialSpannerEditVisitor(Spanner* spanner, - const SharedObjectPointer& material, const QColor& color) : - MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getVoxelColorAttribute() << - AttributeRegistry::getInstance()->getVoxelHermiteAttribute() << - AttributeRegistry::getInstance()->getVoxelMaterialAttribute(), QVector() << - AttributeRegistry::getInstance()->getVoxelColorAttribute() << - AttributeRegistry::getInstance()->getVoxelHermiteAttribute() << - AttributeRegistry::getInstance()->getVoxelMaterialAttribute()), - _spanner(spanner), - _material(material), - _color(color), - _blockSize(spanner->getVoxelizationGranularity() * VOXEL_BLOCK_SIZE) { -} - -int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) { - Box bounds = info.getBounds(); - if (!bounds.intersects(_spanner->getBounds())) { - return STOP_RECURSION; - } - if (info.size > _blockSize) { - return DEFAULT_ORDER; - } - 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); - } - - QVector hermiteContents; - VoxelHermiteDataPointer hermitePointer = info.inputValues.at(1).getInlineValue(); - if (hermitePointer && hermitePointer->getSize() == VOXEL_BLOCK_SAMPLES) { - hermiteContents = hermitePointer->getContents(); - } else { - hermiteContents = QVector(VOXEL_BLOCK_VOLUME * VoxelHermiteData::EDGE_COUNT); - } - - QByteArray materialContents; - QVector materials; - VoxelMaterialDataPointer materialPointer = info.inputValues.at(2).getInlineValue(); - if (materialPointer && materialPointer->getSize() == VOXEL_BLOCK_SAMPLES) { - materialContents = materialPointer->getContents(); - materials = materialPointer->getMaterials(); - } else { - materialContents = QByteArray(VOXEL_BLOCK_VOLUME, 0); - } - - float scale = VOXEL_BLOCK_SIZE / info.size; - QVector colorContents = oldColorContents; - - Box overlap = info.getBounds().getIntersection(_spanner->getBounds()); - overlap.minimum = (overlap.minimum - info.minimum) * scale; - overlap.maximum = (overlap.maximum - info.minimum) * scale; - int minX = glm::ceil(overlap.minimum.x); - int minY = glm::ceil(overlap.minimum.y); - int minZ = glm::ceil(overlap.minimum.z); - int sizeX = (int)overlap.maximum.x - minX + 1; - int sizeY = (int)overlap.maximum.y - minY + 1; - int sizeZ = (int)overlap.maximum.z - minZ + 1; - - bool flipped = false; - float step = 1.0f / scale; - glm::vec3 position(0.0f, 0.0f, info.minimum.z + minZ * step); - if (_spanner->hasOwnColors()) { - for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, - *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) { - position.y = info.minimum.y + minY * step; - for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; - destY += VOXEL_BLOCK_SAMPLES, position.y += step) { - position.x = info.minimum.x + minX * step; - for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) { - if (_spanner->contains(position)) { - *destX = _spanner->getColorAt(position); - } - } - } - } - } else { - QRgb rgb = _color.rgba(); - flipped = (qAlpha(rgb) == 0); - for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, - *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) { - position.y = info.minimum.y + minY * step; - for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; - destY += VOXEL_BLOCK_SAMPLES, position.y += step) { - position.x = info.minimum.x + minX * step; - for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) { - if (_spanner->contains(position)) { - *destX = rgb; - } - } - } - } - } - - // if there are no visible colors, we can clear everything - bool foundOpaque = false; - for (const QRgb* src = colorContents.constData(), *end = src + colorContents.size(); src != end; src++) { - if (qAlpha(*src) != 0) { - foundOpaque = true; - break; - } - } - if (!foundOpaque) { - info.outputValues[0] = AttributeValue(_outputs.at(0)); - info.outputValues[1] = AttributeValue(_outputs.at(1)); - info.outputValues[2] = AttributeValue(_outputs.at(2)); - return STOP_RECURSION; - } - - VoxelColorDataPointer newColorPointer(new VoxelColorData(colorContents, VOXEL_BLOCK_SAMPLES)); - info.outputValues[0] = AttributeValue(info.inputValues.at(0).getAttribute(), - encodeInline(newColorPointer)); - - int hermiteArea = VOXEL_BLOCK_AREA * VoxelHermiteData::EDGE_COUNT; - int hermiteSamples = VOXEL_BLOCK_SAMPLES * VoxelHermiteData::EDGE_COUNT; - int hermiteMinX = minX, hermiteMinY = minY, hermiteMinZ = minZ; - int hermiteSizeX = sizeX, hermiteSizeY = sizeY, hermiteSizeZ = sizeZ; - if (minX > 0) { - hermiteMinX--; - hermiteSizeX++; - } - if (minY > 0) { - hermiteMinY--; - hermiteSizeY++; - } - if (minZ > 0) { - hermiteMinZ--; - hermiteSizeZ++; - } - const int EIGHT_BIT_MAXIMUM = 255; - QRgb* hermiteDestZ = hermiteContents.data() + hermiteMinZ * hermiteArea + hermiteMinY * hermiteSamples + - hermiteMinX * VoxelHermiteData::EDGE_COUNT; - for (int z = hermiteMinZ, hermiteMaxZ = z + hermiteSizeZ - 1; z <= hermiteMaxZ; z++, hermiteDestZ += hermiteArea) { - QRgb* hermiteDestY = hermiteDestZ; - for (int y = hermiteMinY, hermiteMaxY = y + hermiteSizeY - 1; y <= hermiteMaxY; y++, hermiteDestY += hermiteSamples) { - QRgb* hermiteDestX = hermiteDestY; - for (int x = hermiteMinX, hermiteMaxX = x + hermiteSizeX - 1; x <= hermiteMaxX; x++, - hermiteDestX += VoxelHermiteData::EDGE_COUNT) { - // at each intersected non-terminal edge, we check for a transition and, if one is detected, we assign the - // crossing and normal values based on intersection with the sphere - float distance; - glm::vec3 normal; - if (x != VOXEL_BLOCK_SIZE) { - int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; - const QRgb* color = colorContents.constData() + offset; - int alpha0 = qAlpha(color[0]); - int alpha1 = qAlpha(color[1]); - if (alpha0 != alpha1) { - if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step, - info.minimum + glm::vec3(x + 1, y, z) * step, distance, normal)) { - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) { - int alpha = distance * EIGHT_BIT_MAXIMUM; - if (normal.x < 0.0f ? alpha <= qAlpha(hermiteDestX[0]) : alpha >= qAlpha(hermiteDestX[0])) { - hermiteDestX[0] = packNormal(flipped ? -normal : normal, alpha); - } - } else { - hermiteDestX[0] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM); - } - } - } else { - hermiteDestX[0] = 0x0; - } - } else { - hermiteDestX[0] = 0x0; - } - if (y != VOXEL_BLOCK_SIZE) { - int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; - const QRgb* color = colorContents.constData() + offset; - int alpha0 = qAlpha(color[0]); - int alpha2 = qAlpha(color[VOXEL_BLOCK_SAMPLES]); - if (alpha0 != alpha2) { - if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step, - info.minimum + glm::vec3(x, y + 1, z) * step, distance, normal)) { - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) { - int alpha = distance * EIGHT_BIT_MAXIMUM; - if (normal.y < 0.0f ? alpha <= qAlpha(hermiteDestX[1]) : alpha >= qAlpha(hermiteDestX[1])) { - hermiteDestX[1] = packNormal(flipped ? -normal : normal, alpha); - } - } else { - hermiteDestX[1] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM); - } - } - } else { - hermiteDestX[1] = 0x0; - } - } else { - hermiteDestX[1] = 0x0; - } - if (z != VOXEL_BLOCK_SIZE) { - int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; - const QRgb* color = colorContents.constData() + offset; - int alpha0 = qAlpha(color[0]); - int alpha4 = qAlpha(color[VOXEL_BLOCK_AREA]); - if (alpha0 != alpha4) { - if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step, - info.minimum + glm::vec3(x, y, z + 1) * step, distance, normal)) { - const QRgb* oldColor = oldColorContents.constData() + offset; - if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) { - int alpha = distance * EIGHT_BIT_MAXIMUM; - if (normal.z < 0.0f ? alpha <= qAlpha(hermiteDestX[2]) : alpha >= qAlpha(hermiteDestX[2])) { - hermiteDestX[2] = packNormal(flipped ? -normal : normal, alpha); - } - } else { - hermiteDestX[2] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM); - } - } - } else { - hermiteDestX[2] = 0x0; - } - } else { - hermiteDestX[2] = 0x0; - } - } - } - } - VoxelHermiteDataPointer newHermitePointer(new VoxelHermiteData(hermiteContents, VOXEL_BLOCK_SAMPLES)); - info.outputValues[1] = AttributeValue(info.inputValues.at(1).getAttribute(), - encodeInline(newHermitePointer)); - - if (_spanner->hasOwnMaterials()) { - QHash materialMap; - position.z = info.minimum.z + minZ * step; - for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, - *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) { - position.y = info.minimum.y + minY * step; - for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; - destY += VOXEL_BLOCK_SAMPLES, position.y += step) { - position.x = info.minimum.x + minX * step; - for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) { - if (_spanner->contains(position)) { - int material = _spanner->getMaterialAt(position); - if (material != 0) { - int& mapping = materialMap[material]; - if (mapping == 0) { - mapping = getMaterialIndex(_spanner->getMaterials().at(material - 1), materials, - materialContents); - } - material = mapping; - } - *destX = material; - } - } - } - } - } else { - uchar materialIndex = getMaterialIndex(_material, materials, materialContents); - position.z = info.minimum.z + minZ * step; - for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, - *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) { - position.y = info.minimum.y + minY * step; - for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; - destY += VOXEL_BLOCK_SAMPLES, position.y += step) { - position.x = info.minimum.x + minX * step; - for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) { - if (_spanner->contains(position)) { - *destX = materialIndex; - } - } - } - } - } - clearUnusedMaterials(materials, materialContents); - VoxelMaterialDataPointer newMaterialPointer(new VoxelMaterialData(materialContents, VOXEL_BLOCK_SAMPLES, materials)); - info.outputValues[2] = AttributeValue(_inputs.at(2), encodeInline(newMaterialPointer)); - - return STOP_RECURSION; -} - -void VoxelMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { - // expand to fit the entire edit - Spanner* spanner = static_cast(this->spanner.data()); - while (!data.getBounds().contains(spanner->getBounds())) { - data.expand(); - } - // make sure it's either 100% transparent or 100% opaque - QColor color = averageColor; - color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f); - - // find the bounds of all voxel nodes intersected - float nodeSize = VOXEL_BLOCK_SIZE * glm::pow(2.0f, glm::floor( - glm::log(spanner->getVoxelizationGranularity()) / glm::log(2.0f))); - Box bounds(glm::floor(spanner->getBounds().minimum / nodeSize) * nodeSize, - glm::ceil(spanner->getBounds().maximum / nodeSize) * nodeSize); - - // expand to include edges - Box expandedBounds = bounds; - float increment = nodeSize / VOXEL_BLOCK_SIZE; - expandedBounds.maximum.x += increment; - expandedBounds.maximum.z += increment; - - // get all intersecting spanners - QVector results; - data.getIntersecting(AttributeRegistry::getInstance()->getSpannersAttribute(), expandedBounds, results); - - // clear/voxelize as appropriate - SharedObjectPointer heightfield; - foreach (const SharedObjectPointer& result, results) { - Spanner* newSpanner = static_cast(result.data())->clearAndFetchHeight(bounds, heightfield); - if (newSpanner != result) { - data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), result, newSpanner); - } - } - - // voxelize the fetched heightfield, if any - if (heightfield) { - VoxelMaterialSpannerEditVisitor visitor(static_cast(heightfield.data()), material, color); - data.guide(visitor); - } - - VoxelMaterialSpannerEditVisitor visitor(spanner, material, color); - data.guide(visitor); -} - -PaintVoxelMaterialEdit::PaintVoxelMaterialEdit(const glm::vec3& position, float radius, - const SharedObjectPointer& material, const QColor& averageColor) : - MaterialEdit(material, averageColor), - position(position), - radius(radius) { -} - -class PaintVoxelMaterialEditVisitor : public MetavoxelVisitor { -public: - - PaintVoxelMaterialEditVisitor(const glm::vec3& position, float radius, - const SharedObjectPointer& material, const QColor& color); - - virtual int visit(MetavoxelInfo& info); - -private: - - glm::vec3 _position; - float _radius; - SharedObjectPointer _material; - QColor _color; - Box _bounds; -}; - -PaintVoxelMaterialEditVisitor::PaintVoxelMaterialEditVisitor(const glm::vec3& position, float radius, - const SharedObjectPointer& material, const QColor& color) : - MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getVoxelColorAttribute() << - AttributeRegistry::getInstance()->getVoxelMaterialAttribute(), QVector() << - AttributeRegistry::getInstance()->getVoxelColorAttribute() << - AttributeRegistry::getInstance()->getVoxelMaterialAttribute()), - _position(position), - _radius(radius), - _material(material), - _color(color) { - - glm::vec3 extents(_radius, _radius, _radius); - _bounds = Box(_position - extents, _position + extents); -} - -int PaintVoxelMaterialEditVisitor::visit(MetavoxelInfo& info) { - if (!info.getBounds().intersects(_bounds)) { - return STOP_RECURSION; - } - if (!info.isLeaf) { - return DEFAULT_ORDER; - } - VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue(); - VoxelMaterialDataPointer materialPointer = info.inputValues.at(1).getInlineValue(); - if (!(colorPointer && materialPointer && colorPointer->getSize() == materialPointer->getSize())) { - return STOP_RECURSION; - } - QVector colorContents = colorPointer->getContents(); - QByteArray materialContents = materialPointer->getContents(); - QVector materials = materialPointer->getMaterials(); - - Box overlap = info.getBounds().getIntersection(_bounds); - int size = colorPointer->getSize(); - int area = size * size; - float scale = (size - 1.0f) / info.size; - overlap.minimum = (overlap.minimum - info.minimum) * scale; - overlap.maximum = (overlap.maximum - info.minimum) * scale; - int minX = glm::ceil(overlap.minimum.x); - int minY = glm::ceil(overlap.minimum.y); - int minZ = glm::ceil(overlap.minimum.z); - int sizeX = (int)overlap.maximum.x - minX + 1; - int sizeY = (int)overlap.maximum.y - minY + 1; - int sizeZ = (int)overlap.maximum.z - minZ + 1; - - QRgb rgb = _color.rgba(); - float step = 1.0f / scale; - glm::vec3 position(0.0f, 0.0f, info.minimum.z + minZ * step); - uchar materialIndex = getMaterialIndex(_material, materials, materialContents); - QRgb* colorData = colorContents.data(); - uchar* materialData = (uchar*)materialContents.data(); - for (int destZ = minZ * area + minY * size + minX, endZ = destZ + sizeZ * area; destZ != endZ; - destZ += area, position.z += step) { - position.y = info.minimum.y + minY * step; - for (int destY = destZ, endY = destY + sizeY * size; destY != endY; destY += size, position.y += step) { - position.x = info.minimum.x + minX * step; - for (int destX = destY, endX = destX + sizeX; destX != endX; destX++, position.x += step) { - QRgb& color = colorData[destX]; - if (qAlpha(color) != 0 && glm::distance(position, _position) <= _radius) { - color = rgb; - materialData[destX] = materialIndex; - } - } - } - } - VoxelColorDataPointer newColorPointer(new VoxelColorData(colorContents, size)); - info.outputValues[0] = AttributeValue(info.inputValues.at(0).getAttribute(), - encodeInline(newColorPointer)); - - clearUnusedMaterials(materials, materialContents); - VoxelMaterialDataPointer newMaterialPointer(new VoxelMaterialData(materialContents, size, materials)); - info.outputValues[1] = AttributeValue(_inputs.at(1), encodeInline(newMaterialPointer)); - - return STOP_RECURSION; -} - -void PaintVoxelMaterialEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { - // make sure it's 100% opaque - QColor color = averageColor; - color.setAlphaF(1.0f); - PaintVoxelMaterialEditVisitor visitor(position, radius, material, color); - data.guide(visitor); -} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 10477e0486..71013996c2 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -203,8 +203,11 @@ public: STREAM glm::vec3 position; STREAM float radius; STREAM float height; + STREAM bool set; + STREAM bool erase; - PaintHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, float height = 0.0f); + PaintHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, + float height = 0.0f, bool set = false, bool erase = false); virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; }; @@ -225,54 +228,39 @@ public: DECLARE_STREAMABLE_METATYPE(MaterialEdit) -/// An edit that sets a region of a heightfield material. -class PaintHeightfieldMaterialEdit : STREAM public MaterialEdit { - STREAMABLE - -public: - - STREAM glm::vec3 position; - STREAM float radius; - - PaintHeightfieldMaterialEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, - const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor()); - - virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; -}; - -DECLARE_STREAMABLE_METATYPE(PaintHeightfieldMaterialEdit) - -/// An edit that sets the materials of voxels within a spanner to a value. -class VoxelMaterialSpannerEdit : STREAM public MaterialEdit { +/// An edit that sets the materials of a heightfield within a spanner to a value. +class HeightfieldMaterialSpannerEdit : STREAM public MaterialEdit { STREAMABLE public: STREAM SharedObjectPointer spanner; + STREAM bool paint; + STREAM bool voxelize; - VoxelMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(), - const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor()); + HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(), + const SharedObjectPointer& material = SharedObjectPointer(), + const QColor& averageColor = QColor(), bool paint = false, bool voxelize = false); virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; }; -DECLARE_STREAMABLE_METATYPE(VoxelMaterialSpannerEdit) +DECLARE_STREAMABLE_METATYPE(HeightfieldMaterialSpannerEdit) -/// An edit that sets a region of a voxel material. -class PaintVoxelMaterialEdit : STREAM public MaterialEdit { +/// An edit that fills a region of a heightfield height. +class FillHeightfieldHeightEdit : public MetavoxelEdit { STREAMABLE public: STREAM glm::vec3 position; STREAM float radius; - - PaintVoxelMaterialEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, - const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor()); + + FillHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f); virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; }; -DECLARE_STREAMABLE_METATYPE(PaintVoxelMaterialEdit) +DECLARE_STREAMABLE_METATYPE(FillHeightfieldHeightEdit) #endif // hifi_MetavoxelMessages_h diff --git a/libraries/metavoxels/src/Spanner.cpp b/libraries/metavoxels/src/Spanner.cpp index 30bc1f5a59..e890821ec2 100644 --- a/libraries/metavoxels/src/Spanner.cpp +++ b/libraries/metavoxels/src/Spanner.cpp @@ -34,6 +34,7 @@ REGISTER_META_OBJECT(Heightfield) REGISTER_META_OBJECT(Sphere) REGISTER_META_OBJECT(Cuboid) REGISTER_META_OBJECT(StaticModel) +REGISTER_META_OBJECT(MaterialObject) static int heightfieldHeightTypeId = registerSimpleMetaType(); static int heightfieldColorTypeId = registerSimpleMetaType(); @@ -110,16 +111,16 @@ bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& dire return _bounds.findRayIntersection(origin, direction, distance); } -Spanner* Spanner::paintMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material, - const QColor& color) { +Spanner* Spanner::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase) { return this; } -Spanner* Spanner::paintHeight(const glm::vec3& position, float radius, float height) { +Spanner* Spanner::fillHeight(const glm::vec3& position, float radius) { return this; } -Spanner* Spanner::clearAndFetchHeight(const Box& bounds, SharedObjectPointer& heightfield) { +Spanner* Spanner::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, + const QColor& color, bool paint, bool voxelize) { return this; } @@ -361,6 +362,9 @@ QByteArray StaticModel::getRendererClassName() const { return "StaticModelRenderer"; } +DataBlock::~DataBlock() { +} + const int HeightfieldData::SHARED_EDGE = 1; HeightfieldData::HeightfieldData(int width) : @@ -654,17 +658,17 @@ void HeightfieldHeightEditor::select() { QMessageBox::warning(this, "Invalid Image", "The selected image could not be read."); return; } - image = image.convertToFormat(QImage::Format_RGB888); + image = image.convertToFormat(QImage::Format_ARGB32); int width = getHeightfieldSize(image.width()) + 2 * HeightfieldHeight::HEIGHT_BORDER; int height = getHeightfieldSize(image.height()) + 2 * HeightfieldHeight::HEIGHT_BORDER; QVector contents(width * height); quint16* dest = contents.data() + (width + 1) * HeightfieldHeight::HEIGHT_BORDER; const float CONVERSION_SCALE = 65534.0f / numeric_limits::max(); for (int i = 0; i < image.height(); i++, dest += width) { - const uchar* src = image.constScanLine(i); - for (quint16* lineDest = dest, *end = dest + image.width(); lineDest != end; lineDest++, - src += DataBlock::COLOR_BYTES) { - *lineDest = (quint16)(*src * CONVERSION_SCALE) + CONVERSION_OFFSET; + const QRgb* src = (const QRgb*)image.constScanLine(i); + for (quint16* lineDest = dest, *end = dest + image.width(); lineDest != end; lineDest++, src++) { + *lineDest = (qAlpha(*src) < numeric_limits::max()) ? 0 : + (quint16)(qRed(*src) * CONVERSION_SCALE) + CONVERSION_OFFSET; } } emit heightChanged(_height = new HeightfieldHeight(width, contents)); @@ -1114,6 +1118,442 @@ template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const } } +MaterialObject::MaterialObject() : + _scaleS(1.0f), + _scaleT(1.0f) { +} + +int getMaterialIndex(const SharedObjectPointer& material, QVector& materials) { + if (!(material && static_cast(material.data())->getDiffuse().isValid())) { + return 0; + } + // first look for a matching existing material, noting the first reusable slot + int firstEmptyIndex = -1; + for (int i = 0; i < materials.size(); i++) { + const SharedObjectPointer& existingMaterial = materials.at(i); + if (existingMaterial) { + if (existingMaterial->equals(material.data())) { + return i + 1; + } + } else if (firstEmptyIndex == -1) { + firstEmptyIndex = i; + } + } + // if nothing found, use the first empty slot or append + if (firstEmptyIndex != -1) { + materials[firstEmptyIndex] = material; + return firstEmptyIndex + 1; + } + if (materials.size() < numeric_limits::max()) { + materials.append(material); + return materials.size(); + } + return -1; +} + +static QHash countIndices(const QByteArray& contents) { + QHash counts; + for (const uchar* src = (const uchar*)contents.constData(), *end = src + contents.size(); src != end; src++) { + if (*src != 0) { + counts[*src]++; + } + } + return counts; +} + +static uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, + QByteArray& contents) { + int index = getMaterialIndex(material, materials); + if (index != -1) { + return index; + } + // last resort: find the least-used material and remove it + QHash counts = countIndices(contents); + uchar materialIndex = 0; + int lowestCount = INT_MAX; + for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { + if (it.value() < lowestCount) { + materialIndex = it.key(); + lowestCount = it.value(); + } + } + contents.replace((char)materialIndex, (char)0); + return materialIndex; +} + +static void clearUnusedMaterials(QVector& materials, const QByteArray& contents) { + QHash counts = countIndices(contents); + for (int i = 0; i < materials.size(); i++) { + if (counts.value(i + 1) == 0) { + materials[i] = SharedObjectPointer(); + } + } + while (!(materials.isEmpty() || materials.last())) { + materials.removeLast(); + } +} + +static QHash countIndices(const QVector& contents) { + QHash counts; + foreach (const StackArray& array, contents) { + if (array.isEmpty()) { + continue; + } + for (const StackArray::Entry* entry = array.getEntryData(), *end = entry + array.getEntryCount(); + entry != end; entry++) { + if (entry->material != 0) { + counts[entry->material]++; + } + } + } + return counts; +} + +static uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, + QVector& contents) { + int index = getMaterialIndex(material, materials); + if (index != -1) { + return index; + } + // last resort: find the least-used material and remove it + QHash counts = countIndices(contents); + uchar materialIndex = 0; + int lowestCount = INT_MAX; + for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { + if (it.value() < lowestCount) { + materialIndex = it.key(); + lowestCount = it.value(); + } + } + for (StackArray* array = contents.data(), *end = array + contents.size(); array != end; array++) { + if (array->isEmpty()) { + continue; + } + for (StackArray::Entry* entry = array->getEntryData(), *end = entry + array->getEntryCount(); + entry != end; entry++) { + if (entry->material == materialIndex) { + entry->material = 0; + } + } + } + return materialIndex; +} + +static void clearUnusedMaterials(QVector& materials, const QVector& contents) { + QHash counts = countIndices(contents); + for (int i = 0; i < materials.size(); i++) { + if (counts.value(i + 1) == 0) { + materials[i] = SharedObjectPointer(); + } + } + while (!(materials.isEmpty() || materials.last())) { + materials.removeLast(); + } +} + +static QByteArray encodeHeightfieldStack(int offsetX, int offsetY, int width, int height, + const QVector& contents) { + QByteArray inflated(HEIGHTFIELD_DATA_HEADER_SIZE, 0); + qint32* header = (qint32*)inflated.data(); + *header++ = offsetX; + *header++ = offsetY; + *header++ = width; + *header++ = height; + foreach (const StackArray& stack, contents) { + quint16 entries = stack.getEntryCount(); + inflated.append((const char*)&entries, sizeof(quint16)); + inflated.append(stack); + } + return qCompress(inflated); +} + +static QVector decodeHeightfieldStack(const QByteArray& encoded, + int& offsetX, int& offsetY, int& width, int& height) { + QByteArray inflated = qUncompress(encoded); + const qint32* header = (const qint32*)inflated.constData(); + offsetX = *header++; + offsetY = *header++; + width = *header++; + height = *header++; + const char* src = inflated.constData() + HEIGHTFIELD_DATA_HEADER_SIZE; + QVector contents(width * height); + for (StackArray* dest = contents.data(), *end = dest + contents.size(); dest != end; dest++) { + int entries = *(const quint16*)src; + src += sizeof(quint16); + if (entries > 0) { + int bytes = StackArray::getSize(entries); + *dest = StackArray(src, bytes); + src += bytes; + } + } + return contents; +} + +StackArray::Entry::Entry() : + color(0), + material(0), + hermiteX(0), + hermiteY(0), + hermiteZ(0) { +} + +bool StackArray::Entry::isZero() const { + return color == 0 && material == 0 && hermiteX == 0 && hermiteY == 0 && hermiteZ == 0; +} + +bool StackArray::Entry::isMergeable(const Entry& other) const { + return color == other.color && material == other.material && hermiteX == 0 && hermiteY == 0 && hermiteZ == 0; +} + +static inline void setHermite(quint32& value, const glm::vec3& normal, float position) { + value = qRgba(normal.x * numeric_limits::max(), normal.y * numeric_limits::max(), + normal.z * numeric_limits::max(), position * numeric_limits::max()); +} + +static inline float getHermite(QRgb value, glm::vec3& normal) { + normal.x = (char)qRed(value) / (float)numeric_limits::max(); + normal.y = (char)qGreen(value) / (float)numeric_limits::max(); + normal.z = (char)qBlue(value) / (float)numeric_limits::max(); + float length = glm::length(normal); + if (length > 0.0f) { + normal /= length; + } + return qAlpha(value) / (float)numeric_limits::max(); +} + +void StackArray::Entry::setHermiteX(const glm::vec3& normal, float position) { + setHermite(hermiteX, normal, position); +} + +float StackArray::Entry::getHermiteX(glm::vec3& normal) const { + return getHermite(hermiteX, normal); +} + +void StackArray::Entry::setHermiteY(const glm::vec3& normal, float position) { + setHermite(hermiteY, normal, position); +} + +float StackArray::Entry::getHermiteY(glm::vec3& normal) const { + return getHermite(hermiteY, normal); +} + +void StackArray::Entry::setHermiteZ(const glm::vec3& normal, float position) { + setHermite(hermiteZ, normal, position); +} + +float StackArray::Entry::getHermiteZ(glm::vec3& normal) const { + return getHermite(hermiteZ, normal); +} + +int StackArray::getEntryAlpha(int y, float heightfieldHeight) const { + int count = getEntryCount(); + if (count != 0) { + int relative = y - getPosition(); + if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f || y < heightfieldHeight)) { + return qAlpha(getEntryData()[qMax(relative, 0)].color); + } + } + return (heightfieldHeight != 0.0f && y <= heightfieldHeight) ? numeric_limits::max() : 0; +} + +StackArray::Entry& StackArray::getEntry(int y, float heightfieldHeight) { + static Entry emptyEntry; + int count = getEntryCount(); + if (count != 0) { + int relative = y - getPosition(); + if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f || y < heightfieldHeight)) { + return getEntryData()[qMax(relative, 0)]; + } + } + return emptyEntry; +} + +const StackArray::Entry& StackArray::getEntry(int y, float heightfieldHeight) const { + static Entry emptyEntry; + int count = getEntryCount(); + if (count != 0) { + int relative = y - getPosition(); + if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f || y < heightfieldHeight)) { + return getEntryData()[qMax(relative, 0)]; + } + } + return emptyEntry; +} + +void StackArray::getExtents(int& minimumY, int& maximumY) const { + int count = getEntryCount(); + if (count > 0) { + int position = getPosition(); + minimumY = qMin(minimumY, position); + maximumY = qMax(maximumY, position + count - 1); + } +} + +bool StackArray::hasSetEntries() const { + int count = getEntryCount(); + if (count > 0) { + for (const Entry* entry = getEntryData(), *end = entry + count; entry != end; entry++) { + if (entry->isSet()) { + return true; + } + } + } + return false; +} + +HeightfieldStack::HeightfieldStack(int width, const QVector& contents, + const QVector& materials) : + HeightfieldData(width), + _contents(contents), + _materials(materials) { +} + +HeightfieldStack::HeightfieldStack(Bitstream& in, int bytes) { + read(in, bytes); +} + +HeightfieldStack::HeightfieldStack(Bitstream& in, int bytes, const HeightfieldStackPointer& reference) { + if (!reference) { + read(in, bytes); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + reference->setEncodedDelta(in.readAligned(bytes)); + in.readDelta(_materials, reference->getMaterials()); + reference->setDeltaData(DataBlockPointer(this)); + _width = reference->getWidth(); + _contents = reference->getContents(); + + int offsetX, offsetY, width, height; + QVector delta = decodeHeightfieldStack(reference->getEncodedDelta(), offsetX, offsetY, width, height); + if (delta.isEmpty()) { + return; + } + if (offsetX == 0) { + _contents = delta; + _width = width; + return; + } + int minX = offsetX - 1; + int minY = offsetY - 1; + const StackArray* src = delta.constData(); + StackArray* dest = _contents.data() + minY * _width + minX; + for (int y = 0; y < height; y++, src += width, dest += _width) { + const StackArray* lineSrc = src; + for (StackArray* lineDest = dest, *end = dest + width; lineDest != end; lineDest++, lineSrc++) { + *lineDest = *lineSrc; + } + } +} + +void HeightfieldStack::write(Bitstream& out) { + QMutexLocker locker(&_encodedMutex); + if (_encoded.isEmpty()) { + _encoded = encodeHeightfieldStack(0, 0, _width, _contents.size() / _width, _contents); + } + out << _encoded.size(); + out.writeAligned(_encoded); + out << _materials; +} + +void HeightfieldStack::writeDelta(Bitstream& out, const HeightfieldStackPointer& reference) { + if (!reference) { + write(out); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { + if (reference->getWidth() != _width || reference->getContents().size() != _contents.size()) { + reference->setEncodedDelta(encodeHeightfieldStack(0, 0, _width, _contents.size() / _width, _contents)); + + } else { + int height = _contents.size() / _width; + int minX = _width, minY = height; + int maxX = -1, maxY = -1; + const StackArray* src = _contents.constData(); + const StackArray* ref = reference->getContents().constData(); + for (int y = 0; y < height; y++) { + bool difference = false; + for (int x = 0; x < _width; x++) { + if (*src++ != *ref++) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + QVector delta; + int deltaWidth = 0, deltaHeight = 0; + if (maxX >= minX) { + deltaWidth = maxX - minX + 1; + deltaHeight = maxY - minY + 1; + delta = QVector(deltaWidth * deltaHeight); + StackArray* dest = delta.data(); + src = _contents.constData() + minY * _width + minX; + for (int y = 0; y < deltaHeight; y++, src += _width, dest += deltaWidth) { + const StackArray* lineSrc = src; + for (StackArray* lineDest = dest, *end = dest + deltaWidth; lineDest != end; lineDest++, lineSrc++) { + *lineDest = *lineSrc; + } + } + } + reference->setEncodedDelta(encodeHeightfieldStack(minX + 1, minY + 1, deltaWidth, deltaHeight, delta)); + } + reference->setDeltaData(DataBlockPointer(this)); + } + out << reference->getEncodedDelta().size(); + out.writeAligned(reference->getEncodedDelta()); + out.writeDelta(_materials, reference->getMaterials()); +} + +void HeightfieldStack::read(Bitstream& in, int bytes) { + int offsetX, offsetY, height; + _contents = decodeHeightfieldStack(_encoded = in.readAligned(bytes), offsetX, offsetY, _width, height); + in >> _materials; +} + +Bitstream& operator<<(Bitstream& out, const HeightfieldStackPointer& value) { + if (value) { + value->write(out); + } else { + out << 0; + } + return out; +} + +Bitstream& operator>>(Bitstream& in, HeightfieldStackPointer& value) { + int size; + in >> size; + if (size == 0) { + value = HeightfieldStackPointer(); + } else { + value = new HeightfieldStack(in, size); + } + return in; +} + +template<> void Bitstream::writeRawDelta(const HeightfieldStackPointer& value, const HeightfieldStackPointer& reference) { + if (value) { + value->writeDelta(*this, reference); + } else { + *this << 0; + } +} + +template<> void Bitstream::readRawDelta(HeightfieldStackPointer& value, const HeightfieldStackPointer& reference) { + int size; + *this >> size; + if (size == 0) { + value = HeightfieldStackPointer(); + } else { + value = new HeightfieldStack(*this, size, reference); + } +} + bool HeightfieldStreamState::shouldSubdivide() const { return base.lod.shouldSubdivide(minimum, size); } @@ -1144,10 +1584,11 @@ void HeightfieldStreamState::setMinimum(const glm::vec2& lastMinimum, int index) } HeightfieldNode::HeightfieldNode(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, - const HeightfieldMaterialPointer& material) : + const HeightfieldMaterialPointer& material, const HeightfieldStackPointer& stack) : _height(height), _color(color), _material(material), + _stack(stack), _renderer(NULL) { } @@ -1155,6 +1596,7 @@ HeightfieldNode::HeightfieldNode(const HeightfieldNode& other) : _height(other.getHeight()), _color(other.getColor()), _material(other.getMaterial()), + _stack(other.getStack()), _renderer(NULL) { for (int i = 0; i < CHILD_COUNT; i++) { @@ -1169,7 +1611,7 @@ HeightfieldNode::~HeightfieldNode() { const int HEIGHT_LEAF_SIZE = 256 + HeightfieldHeight::HEIGHT_EXTENSION; void HeightfieldNode::setContents(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, - const HeightfieldMaterialPointer& material) { + const HeightfieldMaterialPointer& material, const HeightfieldStackPointer& stack) { clearChildren(); int heightWidth = height->getWidth(); @@ -1246,9 +1688,13 @@ void HeightfieldNode::setContents(const HeightfieldHeightPointer& height, const childMaterial = new HeightfieldMaterial(childMaterialWidth, childMaterialContents, childMaterials); } + HeightfieldStackPointer childStack; + if (stack) { + } + _children[i] = new HeightfieldNode(); _children[i]->setContents(HeightfieldHeightPointer(new HeightfieldHeight(childHeightWidth, childHeightContents)), - childColor, childMaterial); + childColor, childMaterial, childStack); } mergeChildren(); @@ -1263,79 +1709,26 @@ bool HeightfieldNode::isLeaf() const { return true; } -float HeightfieldNode::getHeight(const glm::vec3& location) const { - if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { - return -FLT_MAX; - } - if (!isLeaf()) { - if (location.x < 0.5f) { - if (location.z < 0.5f) { - return _children[0]->getHeight(location * 2.0f); - } else { - return _children[Y_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(0.0f, 0.0f, 1.0f)); - } - } else { - if (location.z < 0.5f) { - return _children[X_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(1.0f, 0.0f, 0.0f)); - } else { - return _children[X_MAXIMUM_FLAG | Y_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(1.0f, 0.0f, 1.0f)); - } - } - } - if (!_height) { - return -FLT_MAX; - } - int width = _height->getWidth(); - const QVector& contents = _height->getContents(); - const quint16* src = contents.constData(); - int height = contents.size() / width; - int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION; - int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION; - - glm::vec3 relative = location; - relative.x = relative.x * innerWidth + HeightfieldHeight::HEIGHT_BORDER; - relative.z = relative.z * innerHeight + HeightfieldHeight::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 = (int)floors.x; - int floorZ = (int)floors.z; - int ceilX = (int)ceils.x; - int ceilZ = (int)ceils.z; - 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); - } - if (interpolatedHeight == 0.0f) { - return -FLT_MAX; // ignore zero values - } - return interpolatedHeight / numeric_limits::max(); -} - -bool HeightfieldNode::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { +bool HeightfieldNode::findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& origin, const glm::vec3& direction, float& distance) const { + glm::quat inverseRotation = glm::inverse(rotation); + glm::vec3 inverseScale = 1.0f / scale; + glm::vec3 transformedOrigin = inverseRotation * (origin - translation) * inverseScale; + glm::vec3 transformedDirection = inverseRotation * direction * inverseScale; float boundsDistance; - if (!Box(glm::vec3(), glm::vec3(1.0f, 1.0f, 1.0f)).findRayIntersection(origin, direction, boundsDistance)) { + if (!Box(glm::vec3(), glm::vec3(1.0f, 1.0f, 1.0f)).findRayIntersection(transformedOrigin, transformedDirection, + boundsDistance)) { return false; } if (!isLeaf()) { float closestDistance = FLT_MAX; for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); float childDistance; - if (_children[i]->findRayIntersection(origin * glm::vec3(2.0f, 1.0f, 2.0f) - - glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f), - direction * glm::vec3(2.0f, 1.0f, 2.0f), childDistance)) { + if (_children[i]->findRayIntersection(translation + + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, + i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, + nextScale, origin, direction, childDistance)) { closestDistance = qMin(closestDistance, childDistance); } } @@ -1345,6 +1738,1373 @@ bool HeightfieldNode::findRayIntersection(const glm::vec3& origin, const glm::ve distance = closestDistance; return true; } + float shortestDistance = FLT_MAX; + float heightfieldDistance; + if (findHeightfieldRayIntersection(transformedOrigin, transformedDirection, boundsDistance, heightfieldDistance)) { + shortestDistance = heightfieldDistance; + } + float rendererDistance; + if (_renderer && _renderer->findRayIntersection(translation, rotation, scale, origin, direction, boundsDistance, + rendererDistance)) { + shortestDistance = qMin(shortestDistance, rendererDistance); + } + if (shortestDistance == FLT_MAX) { + return false; + } + distance = shortestDistance; + return true; +} + +const float HERMITE_GRANULARITY = 1.0f / numeric_limits::max(); + +void HeightfieldNode::getRangeAfterHeightPaint(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& position, float radius, float height, float& minimum, float& maximum) const { + if (!_height) { + return; + } + int heightWidth = _height->getWidth(); + int heightHeight = _height->getContents().size() / heightWidth; + int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; + int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; + int highestHeightX = heightWidth - 1; + int highestHeightZ = heightHeight - 1; + + glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); + glm::vec3 center = glm::inverse(rotation) * (position - translation) * inverseScale + glm::vec3(1.0f, 0.0f, 1.0f); + glm::vec3 extents = radius * inverseScale; + + if (center.x + extents.x < 0.0f || center.z + extents.z < 0.0f || + center.x - extents.x > highestHeightX || center.z - extents.z > highestHeightZ) { + return; + } + if (!isLeaf()) { + for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); + _children[i]->getRangeAfterHeightPaint(translation + + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, + i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, + nextScale, position, radius, height, minimum, maximum); + } + return; + } + glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(), + glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); + glm::vec3 end = glm::clamp(glm::ceil(center + extents), glm::vec3(), + glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); + + const quint16* lineDest = _height->getContents().constData() + (int)start.z * heightWidth + (int)start.x; + float squaredRadius = extents.x * extents.x; + float squaredRadiusReciprocal = 1.0f / squaredRadius; + float multiplierZ = extents.x / extents.z; + float relativeHeight = height * numeric_limits::max() / scale.y; + for (float z = start.z; z <= end.z; z += 1.0f) { + const quint16* dest = lineDest; + for (float x = start.x; x <= end.x; x += 1.0f, dest++) { + float dx = x - center.x, dz = (z - center.z) * multiplierZ; + float distanceSquared = dx * dx + dz * dz; + if (distanceSquared <= squaredRadius) { + // height falls off towards edges + int value = *dest; + if (value != 0) { + value += relativeHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; + minimum = qMin(minimum, (float)value); + maximum = qMax(maximum, (float)value); + } + } + } + lineDest += heightWidth; + } + + // make sure we increment in multiples of the voxel size + float voxelStep = scale.x / innerHeightWidth; + float heightIncrement = (1.0f - minimum) * scale.y / numeric_limits::max(); + float incrementSteps = heightIncrement / voxelStep; + if (glm::abs(incrementSteps - glm::round(incrementSteps)) > HERMITE_GRANULARITY) { + minimum = 1.0f - voxelStep * glm::ceil(incrementSteps) * numeric_limits::max() / scale.y; + } +} + +HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& position, float radius, float height, bool set, bool erase, + float normalizeScale, float normalizeOffset) { + if (!_height) { + return this; + } + int heightWidth = _height->getWidth(); + int heightHeight = _height->getContents().size() / heightWidth; + int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; + int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; + int highestHeightX = heightWidth - 1; + int highestHeightZ = heightHeight - 1; + + glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); + glm::vec3 center = glm::inverse(rotation) * (position - translation) * inverseScale + glm::vec3(1.0f, 0.0f, 1.0f); + glm::vec3 extents = radius * inverseScale; + + bool intersects = (center.x + extents.x >= 0.0f && center.z + extents.z >= 0.0f && + center.x - extents.x <= highestHeightX && center.z - extents.z <= highestHeightZ); + if (!intersects && normalizeScale == 1.0f && normalizeOffset == 0.0f) { + return this; + } + if (!isLeaf()) { + HeightfieldNode* newNode = this; + for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); + HeightfieldNode* newChild = _children[i]->paintHeight(translation + + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, + i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, + nextScale, position, radius, height, set, erase, normalizeScale, normalizeOffset); + if (_children[i] != newChild) { + if (newNode == this) { + newNode = new HeightfieldNode(*this); + } + newNode->setChild(i, HeightfieldNodePointer(newChild)); + } + } + if (newNode != this) { + newNode->mergeChildren(true, false); + } + return newNode; + } + QVector newHeightContents = _height->getContents(); + + int stackWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE; + QVector newStackContents; + QVector newStackMaterials; + if (_stack) { + stackWidth = _stack->getWidth(); + newStackContents = _stack->getContents(); + newStackMaterials = _stack->getMaterials(); + } + int innerStackWidth = stackWidth - HeightfieldData::SHARED_EDGE; + + // renormalize if necessary + maybeRenormalize(scale, normalizeScale, normalizeOffset, innerStackWidth, newHeightContents, newStackContents); + if (!intersects) { + return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), + _color, _material, HeightfieldStackPointer(newStackContents.isEmpty() ? NULL : + new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); + } + + // now apply the actual change + glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(), + glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); + glm::vec3 end = glm::clamp(glm::ceil(center + extents), glm::vec3(), + glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); + + quint16* lineDest = newHeightContents.data() + (int)start.z * heightWidth + (int)start.x; + float squaredRadius = extents.x * extents.x; + float squaredRadiusReciprocal = 1.0f / squaredRadius; + float multiplierZ = extents.x / extents.z; + float relativeHeight = height * numeric_limits::max() / scale.y; + quint16 heightValue = erase ? 0 : relativeHeight; + for (float z = start.z; z <= end.z; z += 1.0f) { + quint16* dest = lineDest; + for (float x = start.x; x <= end.x; x += 1.0f, dest++) { + float dx = x - center.x, dz = (z - center.z) * multiplierZ; + float distanceSquared = dx * dx + dz * dz; + if (distanceSquared <= squaredRadius) { + if (erase || set) { + *dest = heightValue; + + } else { + // height falls off towards edges + int value = *dest; + if (value != 0) { + *dest = value + relativeHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; + } + } + } + } + lineDest += heightWidth; + } + + return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), + _color, _material, HeightfieldStackPointer(newStackContents.isEmpty() ? NULL : + new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); +} + +HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& position, float radius) { + if (!_height) { + return this; + } + int heightWidth = _height->getWidth(); + int heightHeight = _height->getContents().size() / heightWidth; + int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; + int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; + int highestHeightX = heightWidth - 1; + int highestHeightZ = heightHeight - 1; + + glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); + glm::vec3 center = glm::inverse(rotation) * (position - translation) * inverseScale + glm::vec3(1.0f, 0.0f, 1.0f); + glm::vec3 extents = radius * inverseScale; + + if (center.x + extents.x < 0.0f || center.z + extents.z < 0.0f || center.x - extents.x > highestHeightX || + center.z - extents.z > highestHeightZ) { + return this; + } + if (!isLeaf()) { + HeightfieldNode* newNode = this; + for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); + HeightfieldNode* newChild = _children[i]->fillHeight(translation + + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, + i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, + nextScale, position, radius); + if (_children[i] != newChild) { + if (newNode == this) { + newNode = new HeightfieldNode(*this); + } + newNode->setChild(i, HeightfieldNodePointer(newChild)); + } + } + if (newNode != this) { + newNode->mergeChildren(true, false); + } + return newNode; + } + if (!_stack) { + return this; + } + QVector newHeightContents = _height->getContents(); + + QVector newStackContents = _stack->getContents(); + int stackWidth = _stack->getWidth(); + int stackHeight = newStackContents.size() / stackWidth; + QVector newStackMaterials = _stack->getMaterials(); + + int colorWidth, colorHeight; + QByteArray newColorContents; + if (_color) { + colorWidth = _color->getWidth(); + colorHeight = _color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); + newColorContents = _color->getContents(); + + } else { + colorWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE; + colorHeight = innerHeightHeight + HeightfieldData::SHARED_EDGE; + newColorContents = QByteArray(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF); + } + int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; + int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; + + int materialWidth, materialHeight; + QByteArray newMaterialContents; + QVector newMaterialMaterials; + if (_material) { + materialWidth = _material->getWidth(); + materialHeight = _material->getContents().size() / materialWidth; + newMaterialContents = _material->getContents(); + newMaterialMaterials = _material->getMaterials(); + + } else { + materialWidth = colorWidth; + materialHeight = colorHeight; + newMaterialContents = QByteArray(materialWidth * materialHeight, 0); + } + int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; + int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; + + glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(), + glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); + glm::vec3 end = glm::clamp(glm::ceil(center + extents), glm::vec3(), + glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); + float voxelStep = scale.x / innerHeightWidth; + float voxelScale = scale.y / (numeric_limits::max() * voxelStep); + + quint16* lineDest = newHeightContents.data() + (int)start.z * heightWidth + (int)start.x; + float squaredRadius = extents.x * extents.x; + float multiplierZ = extents.x / extents.z; + float colorStepX = (float)innerColorWidth / innerHeightWidth; + float colorStepZ = (float)innerColorHeight / innerHeightHeight; + float materialStepX = (float)innerMaterialWidth / innerHeightWidth; + float materialStepZ = (float)innerMaterialHeight / innerHeightHeight; + QHash materialMap; + for (float z = start.z; z <= end.z; z += 1.0f) { + quint16* dest = lineDest; + for (float x = start.x; x <= end.x; x += 1.0f, dest++) { + float dx = x - center.x, dz = (z - center.z) * multiplierZ; + float distanceSquared = dx * dx + dz * dz; + if (distanceSquared <= squaredRadius && x >= 1.0f && z >= 1.0f && x <= stackWidth && z <= stackHeight) { + int stackX = (int)x - 1, stackZ = (int)z - 1; + StackArray* stackDest = newStackContents.data() + stackZ * stackWidth + stackX; + if (stackDest->isEmpty()) { + continue; + } + int y = stackDest->getPosition() + stackDest->getEntryCount() - 1; + for (const StackArray::Entry* entry = stackDest->getEntryData() + stackDest->getEntryCount() - 1; + entry >= stackDest->getEntryData(); entry--, y--) { + if (!entry->isSet()) { + continue; + } + glm::vec3 normal; + int newHeight = qMax((int)((y + entry->getHermiteY(normal)) / voxelScale), 1); + if (newHeight < *dest) { + break; + } + *dest = newHeight; + for (int colorZ = stackZ * colorStepZ; (int)(colorZ / colorStepZ) == stackZ; colorZ++) { + for (int colorX = stackX * colorStepX; (int)(colorX / colorStepX) == stackX; colorX++) { + uchar* colorDest = (uchar*)newColorContents.data() + + (colorZ * colorWidth + colorX) * DataBlock::COLOR_BYTES; + colorDest[0] = qRed(entry->color); + colorDest[1] = qGreen(entry->color); + colorDest[2] = qBlue(entry->color); + } + } + for (int materialZ = stackZ * materialStepZ; (int)(materialZ / materialStepZ) == stackZ; materialZ++) { + for (int materialX = stackX * materialStepX; (int)(materialX / materialStepX) == stackX; + materialX++) { + int material = entry->material; + if (material != 0) { + int& mapping = materialMap[material]; + if (mapping == 0) { + mapping = getMaterialIndex(newStackMaterials.at(material - 1), + newMaterialMaterials, newMaterialContents); + } + material = mapping; + } + newMaterialContents[materialZ * materialWidth + materialX] = material; + } + } + break; + } + stackDest->clear(); + } + } + lineDest += heightWidth; + } + clearUnusedMaterials(newMaterialMaterials, newMaterialContents); + clearUnusedMaterials(newStackMaterials, newStackContents); + + return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), + HeightfieldColorPointer(new HeightfieldColor(colorWidth, newColorContents)), + HeightfieldMaterialPointer(new HeightfieldMaterial(materialWidth, newMaterialContents, newMaterialMaterials)), + HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); +} + +void HeightfieldNode::getRangeAfterEdit(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const Box& editBounds, float& minimum, float& maximum) const { + if (!_height) { + return; + } + int heightWidth = _height->getWidth(); + int heightHeight = _height->getContents().size() / heightWidth; + int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; + int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; + glm::mat4 baseInverseTransform = glm::mat4_cast(glm::inverse(rotation)) * glm::translate(-translation); + glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); + glm::mat4 inverseTransform = glm::translate(glm::vec3(1.0f, 0.0f, 1.0f)) * glm::scale(inverseScale) * baseInverseTransform; + Box transformedBounds = inverseTransform * editBounds; + if (transformedBounds.maximum.x < 0.0f || transformedBounds.maximum.z < 0.0f || + transformedBounds.minimum.x > heightWidth - 1 || transformedBounds.minimum.z > heightHeight - 1) { + return; + } + if (!isLeaf()) { + for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); + _children[i]->getRangeAfterEdit(translation + + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, + i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, + nextScale, editBounds, minimum, maximum); + } + return; + } + glm::vec3 start = glm::floor(transformedBounds.minimum); + glm::vec3 end = glm::ceil(transformedBounds.maximum); + + minimum = qMin(minimum, start.y); + maximum = qMax(maximum, end.y); + + // make sure we increment in multiples of the voxel size + float voxelStep = scale.x / innerHeightWidth; + float heightIncrement = (1.0f - minimum) * scale.y / numeric_limits::max(); + float incrementSteps = heightIncrement / voxelStep; + if (glm::abs(incrementSteps - glm::round(incrementSteps)) > HERMITE_GRANULARITY) { + minimum = 1.0f - voxelStep * glm::ceil(incrementSteps) * numeric_limits::max() / scale.y; + } +} + +HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize, + float normalizeScale, float normalizeOffset) { + if (!_height) { + return this; + } + int heightWidth = _height->getWidth(); + int heightHeight = _height->getContents().size() / heightWidth; + int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; + int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; + glm::vec3 expansion(1.0f / innerHeightWidth, 0.0f, 1.0f / innerHeightHeight); + Box bounds = glm::translate(translation) * glm::mat4_cast(rotation) * Box(-expansion, scale + expansion); + bool intersects = bounds.intersects(spanner->getBounds()); + if (!intersects && normalizeScale == 1.0f && normalizeOffset == 0.0f) { + return this; + } + if (!isLeaf()) { + HeightfieldNode* newNode = this; + for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); + HeightfieldNode* newChild = _children[i]->setMaterial(translation + + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, + i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, spanner, + material, color, paint, voxelize, normalizeScale, normalizeOffset); + if (_children[i] != newChild) { + if (newNode == this) { + newNode = new HeightfieldNode(*this); + } + newNode->setChild(i, HeightfieldNodePointer(newChild)); + } + } + if (newNode != this) { + newNode->mergeChildren(); + } + return newNode; + } + int highestHeightX = heightWidth - 1; + int highestHeightZ = heightHeight - 1; + QVector newHeightContents = _height->getContents(); + + int stackWidth, stackHeight; + QVector newStackContents; + QVector newStackMaterials; + if (_stack) { + stackWidth = _stack->getWidth(); + stackHeight = _stack->getContents().size() / stackWidth; + newStackContents = _stack->getContents(); + newStackMaterials = _stack->getMaterials(); + + } else { + stackWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE; + stackHeight = innerHeightHeight + HeightfieldData::SHARED_EDGE; + newStackContents = QVector(stackWidth * stackHeight); + } + int innerStackWidth = stackWidth - HeightfieldData::SHARED_EDGE; + int innerStackHeight = stackHeight - HeightfieldData::SHARED_EDGE; + + // renormalize if necessary + maybeRenormalize(scale, normalizeScale, normalizeOffset, innerStackWidth, newHeightContents, newStackContents); + if (!intersects) { + return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), + _color, _material, HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); + } + QVector oldHeightContents = newHeightContents; + QVector oldStackContents = newStackContents; + + int colorWidth, colorHeight; + QByteArray newColorContents; + if (_color) { + colorWidth = _color->getWidth(); + colorHeight = _color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); + newColorContents = _color->getContents(); + + } else { + colorWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE; + colorHeight = innerHeightHeight + HeightfieldData::SHARED_EDGE; + newColorContents = QByteArray(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF); + } + int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; + int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; + + int materialWidth, materialHeight; + QByteArray newMaterialContents; + QVector newMaterialMaterials; + if (_material) { + materialWidth = _material->getWidth(); + materialHeight = _material->getContents().size() / materialWidth; + newMaterialContents = _material->getContents(); + newMaterialMaterials = _material->getMaterials(); + + } else { + materialWidth = colorWidth; + materialHeight = colorHeight; + newMaterialContents = QByteArray(materialWidth * materialHeight, 0); + } + int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; + int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; + + glm::mat4 baseInverseTransform = glm::mat4_cast(glm::inverse(rotation)) * glm::translate(-translation); + glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); + glm::mat4 inverseTransform = glm::translate(glm::vec3(1.0f, 0.0f, 1.0f)) * glm::scale(inverseScale) * baseInverseTransform; + Box transformedBounds = inverseTransform * spanner->getBounds(); + glm::mat4 transform = glm::inverse(inverseTransform); + + glm::vec3 start = glm::ceil(transformedBounds.maximum); + glm::vec3 end = glm::floor(transformedBounds.minimum); + + float stepX = 1.0f, stepZ = 1.0f; + if (paint) { + stepX = (float)innerHeightWidth / qMax(innerHeightWidth, qMax(innerColorWidth, innerMaterialWidth)); + stepZ = (float)innerHeightHeight / qMax(innerHeightHeight, qMax(innerColorHeight, innerMaterialHeight)); + + } else { + start.x += 1.0f; + start.z += 1.0f; + end.x -= 1.0f; + end.z -= 1.0f; + } + + float startX = glm::clamp(start.x, 0.0f, (float)highestHeightX), endX = glm::clamp(end.x, 0.0f, (float)highestHeightX); + float startZ = glm::clamp(start.z, 0.0f, (float)highestHeightZ), endZ = glm::clamp(end.z, 0.0f, (float)highestHeightZ); + float voxelStep = scale.x / innerHeightWidth; + float voxelScale = scale.y / (numeric_limits::max() * voxelStep); + int newTop = start.y * voxelScale; + int newBottom = end.y * voxelScale; + glm::vec3 worldStart = glm::vec3(transform * glm::vec4(startX, paint ? 0.0f : newTop / voxelScale, startZ, 1.0f)); + glm::vec3 worldStepX = glm::vec3(transform * glm::vec4(stepX, 0.0f, 0.0f, 0.0f)); + glm::vec3 worldStepY = glm::vec3(transform * glm::vec4(0.0f, 1.0f / voxelScale, 0.0f, 0.0f)); + glm::vec3 worldStepZ = glm::vec3(transform * glm::vec4(0.0f, 0.0f, stepZ, 0.0f)); + QRgb rgba = color.rgba(); + bool erase = (color.alpha() == 0); + uchar materialMaterialIndex = getMaterialIndex(material, newMaterialMaterials, newMaterialContents); + uchar stackMaterialIndex = getMaterialIndex(material, newStackMaterials, newStackContents); + bool hasOwnColors = spanner->hasOwnColors(); + bool hasOwnMaterials = spanner->hasOwnMaterials(); + QHash materialMappings; + for (float z = startZ; z >= endZ; z -= stepZ, worldStart -= worldStepZ) { + quint16* heightDest = newHeightContents.data() + (int)z * heightWidth; + glm::vec3 worldPos = worldStart; + for (float x = startX; x >= endX; x -= stepX, worldPos -= worldStepX) { + quint16* heightLineDest = heightDest + (int)x; + float distance; + glm::vec3 normal; + + float colorX = (x - HeightfieldHeight::HEIGHT_BORDER) * innerColorWidth / innerHeightWidth; + float colorZ = (z - HeightfieldHeight::HEIGHT_BORDER) * innerColorHeight / innerHeightHeight; + uchar* colorDest = (colorX >= 0.0f && colorX <= innerColorWidth && colorZ >= 0.0f && colorZ <= innerColorHeight) ? + ((uchar*)newColorContents.data() + ((int)colorZ * colorWidth + (int)colorX) * DataBlock::COLOR_BYTES) : NULL; + + float materialX = (x - HeightfieldHeight::HEIGHT_BORDER) * innerMaterialWidth / innerHeightWidth; + float materialZ = (z - HeightfieldHeight::HEIGHT_BORDER) * innerMaterialHeight / innerHeightHeight; + char* materialDest = (materialX >= 0.0f && materialX <= innerMaterialWidth && materialZ >= 0.0f && + materialZ <= innerMaterialHeight) ? (newMaterialContents.data() + + (int)materialZ * materialWidth + (int)materialX) : NULL; + + if (paint && *heightLineDest != 0 && spanner->contains(worldPos + worldStepY * (*heightLineDest * voxelScale))) { + if (colorDest) { + colorDest[0] = qRed(rgba); + colorDest[1] = qGreen(rgba); + colorDest[2] = qBlue(rgba); + } + if (materialDest) { + *materialDest = materialMaterialIndex; + } + } + + float stackX = (x - HeightfieldHeight::HEIGHT_BORDER) * innerStackWidth / innerHeightWidth; + float stackZ = (z - HeightfieldHeight::HEIGHT_BORDER) * innerStackHeight / innerHeightHeight; + + if (stackX >= 0.0f && stackX <= innerStackWidth && stackZ >= 0.0f && stackZ <= innerStackHeight) { + StackArray* stackDest = newStackContents.data() + (int)stackZ * stackWidth + (int)stackX; + if (paint) { + if (stackDest->isEmpty() || glm::fract(x) != 0.0f || glm::fract(z) != 0.0f) { + continue; + } + glm::vec3 pos = worldPos + worldStepY * (float)stackDest->getPosition(); + for (StackArray::Entry* entryDest = stackDest->getEntryData(), *end = entryDest + + stackDest->getEntryCount(); entryDest != end; entryDest++, pos += worldStepY) { + if (entryDest->isSet() && spanner->contains(pos)) { + entryDest->color = rgba; + entryDest->material = stackMaterialIndex; + } + } + continue; + } + int prepend = 0, append = 0; + if (!stackDest->isEmpty()) { + int oldBottom = stackDest->getPosition(); + int oldTop = oldBottom + stackDest->getEntryCount() - 1; + prepend = qMax(0, oldBottom - newBottom); + append = qMax(0, newTop - oldTop); + if (prepend != 0 || append != 0) { + StackArray newStack(prepend + stackDest->getEntryCount() + append); + memcpy(newStack.getEntryData() + prepend, stackDest->getEntryData(), + stackDest->getEntryCount() * sizeof(StackArray::Entry)); + for (StackArray::Entry* entryDest = newStack.getEntryData(), *end = entryDest + prepend; + entryDest != end; entryDest++) { + entryDest->color = end->color; + entryDest->material = end->material; + } + *stackDest = newStack; + stackDest->setPosition(qMin(oldBottom, newBottom)); + } + } else { + *stackDest = StackArray(newTop - newBottom + 1); + stackDest->setPosition(newBottom); + prepend = stackDest->getEntryCount(); + } + const quint16* oldHeightLineDest = oldHeightContents.constData() + (int)z * heightWidth + (int)x; + if (*heightLineDest != 0) { + float voxelHeight = *heightLineDest * voxelScale; + float left = oldHeightLineDest[-1] * voxelScale; + float right = oldHeightLineDest[1] * voxelScale; + float down = oldHeightLineDest[-heightWidth] * voxelScale; + float up = oldHeightLineDest[heightWidth] * voxelScale; + float deltaX = (left == 0.0f || right == 0.0f) ? 0.0f : (left - right); + float deltaZ = (up == 0.0f || down == 0.0f) ? 0.0f : (down - up); + for (int i = 0, total = prepend + append; i < total; i++) { + int offset = (i < prepend) ? i : stackDest->getEntryCount() - append + (i - prepend); + int y = stackDest->getPosition() + offset; + StackArray::Entry* entryDest = stackDest->getEntryData() + offset; + if (y > voxelHeight) { + if (y <= right) { + entryDest->setHermiteX(glm::normalize(glm::vec3(voxelHeight - right, 1.0f, deltaZ * 0.5f)), + (right == voxelHeight) ? 0.5f : (y - voxelHeight) / (right - voxelHeight)); + } + if (y <= up) { + entryDest->setHermiteZ(glm::normalize(glm::vec3(deltaX * 0.5f, 1.0f, voxelHeight - up)), + (up == voxelHeight) ? 0.5f : (y - voxelHeight) / (up - voxelHeight)); + } + } else { + if (right != 0.0f && y > right) { + entryDest->setHermiteX(glm::normalize(glm::vec3(voxelHeight - right, 1.0f, deltaZ * 0.5f)), + (right == voxelHeight) ? 0.5f : (y - voxelHeight) / (right - voxelHeight)); + } + if (up != 0.0f && y > up) { + entryDest->setHermiteZ(glm::normalize(glm::vec3(deltaX * 0.5f, 1.0f, voxelHeight - up)), + (up == voxelHeight) ? 0.5f : (y - voxelHeight) / (up - voxelHeight)); + } + if (colorDest) { + entryDest->color = qRgb(colorDest[0], colorDest[1], colorDest[2]); + } + if (materialDest) { + int index = *materialDest; + if (index != 0) { + int& mapping = materialMappings[index]; + if (mapping == 0) { + mapping = getMaterialIndex(newMaterialMaterials.at(index - 1), + newStackMaterials, newStackContents); + } + index = mapping; + } + entryDest->material = index; + } + if (y + 1 > voxelHeight) { + *heightLineDest = 0; + entryDest->setHermiteY(glm::normalize(glm::vec3(deltaX, 2.0f, deltaZ)), voxelHeight - y); + } + } + } + } + StackArray::Entry* entryDest = stackDest->getEntryData() + (newTop - stackDest->getPosition()); + glm::vec3 pos = worldPos; + float voxelHeight = *heightLineDest * voxelScale; + float nextVoxelHeightX = heightLineDest[1] * voxelScale; + float nextVoxelHeightZ = heightLineDest[heightWidth] * voxelScale; + float oldVoxelHeight = *oldHeightLineDest * voxelScale; + float oldNextVoxelHeightX = oldHeightLineDest[1] * voxelScale; + float oldNextVoxelHeightZ = oldHeightLineDest[heightWidth] * voxelScale; + // skip the actual set if voxelizing + for (int y = voxelize ? newBottom - 1 : newTop; y >= newBottom; y--, entryDest--, pos -= worldStepY) { + int oldCurrentAlpha = stackDest->getEntryAlpha(y, oldVoxelHeight); + if (spanner->contains(pos)) { + if (hasOwnColors && !erase) { + entryDest->color = spanner->getColorAt(pos); + + } else { + entryDest->color = rgba; + } + if (hasOwnMaterials && !erase) { + int index = spanner->getMaterialAt(pos); + if (index != 0) { + int& mapping = materialMappings[index]; + if (mapping == 0) { + mapping = getMaterialIndex(spanner->getMaterials().at(index - 1), + newStackMaterials, newStackContents); + } + index = mapping; + } + entryDest->material = index; + + } else { + entryDest->material = stackMaterialIndex; + } + } + + int currentAlpha = stackDest->getEntryAlpha(y, voxelHeight); + bool flipped = (color.alpha() == currentAlpha); + int nextStackX = (int)stackX + 1; + if (nextStackX <= innerStackWidth) { + int nextAlphaX = newStackContents.at((int)stackZ * stackWidth + nextStackX).getEntryAlpha( + y, nextVoxelHeightX); + if (nextAlphaX == currentAlpha) { + entryDest->hermiteX = 0; + + } else { + float oldDistance = flipped ? 0.0f : 1.0f; + if (currentAlpha == oldCurrentAlpha && nextAlphaX == oldStackContents.at((int)stackZ * stackWidth + + nextStackX).getEntryAlpha(y, oldNextVoxelHeightX) && entryDest->hermiteX != 0) { + oldDistance = qAlpha(entryDest->hermiteX) / (float)numeric_limits::max(); + } + if (flipped ? (spanner->intersects(pos + worldStepX, pos, distance, normal) && + (distance = 1.0f - distance) >= oldDistance) : + (spanner->intersects(pos, pos + worldStepX, distance, normal) && + distance <= oldDistance)) { + entryDest->setHermiteX(erase ? -normal : normal, distance); + } + } + } + bool nextAlphaY = stackDest->getEntryAlpha(y + 1, voxelHeight); + if (nextAlphaY == currentAlpha) { + entryDest->hermiteY = 0; + + } else { + float oldDistance = flipped ? 0.0f : 1.0f; + if (currentAlpha == oldCurrentAlpha && nextAlphaY == oldStackContents.at((int)stackZ * stackWidth + + (int)stackX).getEntryAlpha(y + 1, oldVoxelHeight) && entryDest->hermiteY != 0) { + oldDistance = qAlpha(entryDest->hermiteY) / (float)numeric_limits::max(); + } + if (flipped ? (spanner->intersects(pos + worldStepY, pos, distance, normal) && + (distance = 1.0f - distance) >= oldDistance) : + (spanner->intersects(pos, pos + worldStepY, distance, normal) && + distance <= oldDistance)) { + entryDest->setHermiteY(erase ? -normal : normal, distance); + } + } + int nextStackZ = (int)stackZ + 1; + if (nextStackZ <= innerStackHeight) { + bool nextAlphaZ = newStackContents.at(nextStackZ * stackWidth + (int)stackX).getEntryAlpha( + y, nextVoxelHeightZ); + if (nextAlphaZ == currentAlpha) { + entryDest->hermiteZ = 0; + + } else { + float oldDistance = flipped ? 0.0f : 1.0f; + if (currentAlpha == oldCurrentAlpha && nextAlphaZ == oldStackContents.at(nextStackZ * stackWidth + + (int)stackX).getEntryAlpha(y, oldNextVoxelHeightZ) && entryDest->hermiteZ != 0) { + oldDistance = qAlpha(entryDest->hermiteZ) / (float)numeric_limits::max(); + } + if (flipped ? (spanner->intersects(pos + worldStepZ, pos, distance, normal) && + (distance = 1.0f - distance) >= oldDistance) : + (spanner->intersects(pos, pos + worldStepZ, distance, normal) && + distance <= oldDistance)) { + entryDest->setHermiteZ(erase ? -normal : normal, distance); + } + } + } + } + + // prune zero entries from end, repeated entries from beginning + int endPruneCount = 0; + for (int i = stackDest->getEntryCount() - 1; i >= 0 && stackDest->getEntryData()[i].isZero(); i--) { + endPruneCount++; + } + if (endPruneCount == stackDest->getEntryCount()) { + stackDest->clear(); + + } else { + stackDest->removeEntries(stackDest->getEntryCount() - endPruneCount, endPruneCount); + int beginningPruneCount = 0; + for (int i = 0; i < stackDest->getEntryCount() - 1 && stackDest->getEntryData()[i].isMergeable( + stackDest->getEntryData()[i + 1]); i++) { + beginningPruneCount++; + } + stackDest->removeEntries(0, beginningPruneCount); + stackDest->getPositionRef() += beginningPruneCount; + } + } + } + } + clearUnusedMaterials(newMaterialMaterials, newMaterialContents); + clearUnusedMaterials(newStackMaterials, newStackContents); + + return new HeightfieldNode(paint ? _height : HeightfieldHeightPointer( + new HeightfieldHeight(heightWidth, newHeightContents)), + HeightfieldColorPointer(new HeightfieldColor(colorWidth, newColorContents)), + HeightfieldMaterialPointer(new HeightfieldMaterial(materialWidth, newMaterialContents, newMaterialMaterials)), + HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); +} + +void HeightfieldNode::read(HeightfieldStreamState& state) { + clearChildren(); + + if (!state.shouldSubdivide()) { + state.base.stream >> _height >> _color >> _material >> _stack; + return; + } + bool leaf; + state.base.stream >> leaf; + if (leaf) { + state.base.stream >> _height >> _color >> _material >> _stack; + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i] = new HeightfieldNode(); + _children[i]->read(nextState); + } + mergeChildren(); + } +} + +void HeightfieldNode::write(HeightfieldStreamState& state) const { + if (!state.shouldSubdivide()) { + state.base.stream << _height << _color << _material << _stack; + return; + } + bool leaf = isLeaf(); + state.base.stream << leaf; + if (leaf) { + state.base.stream << _height << _color << _material << _stack; + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->write(nextState); + } + } +} + +void HeightfieldNode::readDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) { + clearChildren(); + + if (!state.shouldSubdivide()) { + state.base.stream.readDelta(_height, reference->getHeight()); + state.base.stream.readDelta(_color, reference->getColor()); + state.base.stream.readDelta(_material, reference->getMaterial()); + state.base.stream.readDelta(_stack, reference->getStack()); + return; + } + bool leaf; + state.base.stream >> leaf; + if (leaf) { + state.base.stream.readDelta(_height, reference->getHeight()); + state.base.stream.readDelta(_color, reference->getColor()); + state.base.stream.readDelta(_material, reference->getMaterial()); + state.base.stream.readDelta(_stack, reference->getStack()); + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + if (reference->isLeaf() || !state.shouldSubdivideReference()) { + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i] = new HeightfieldNode(); + _children[i]->read(nextState); + } + } else { + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + bool changed; + state.base.stream >> changed; + if (changed) { + _children[i] = new HeightfieldNode(); + _children[i]->readDelta(reference->getChild(i), nextState); + } else { + if (nextState.becameSubdividedOrCollapsed()) { + _children[i] = reference->getChild(i)->readSubdivision(nextState); + + } else { + _children[i] = reference->getChild(i); + } + } + } + } + mergeChildren(); + } +} + +void HeightfieldNode::writeDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) const { + if (!state.shouldSubdivide()) { + state.base.stream.writeDelta(_height, reference->getHeight()); + state.base.stream.writeDelta(_color, reference->getColor()); + state.base.stream.writeDelta(_material, reference->getMaterial()); + state.base.stream.writeDelta(_stack, reference->getStack()); + return; + } + bool leaf = isLeaf(); + state.base.stream << leaf; + if (leaf) { + state.base.stream.writeDelta(_height, reference->getHeight()); + state.base.stream.writeDelta(_color, reference->getColor()); + state.base.stream.writeDelta(_material, reference->getMaterial()); + state.base.stream.writeDelta(_stack, reference->getStack()); + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + if (reference->isLeaf() || !state.shouldSubdivideReference()) { + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->write(nextState); + } + } else { + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + if (_children[i] == reference->getChild(i)) { + state.base.stream << false; + if (nextState.becameSubdivided()) { + _children[i]->writeSubdivision(nextState); + } + } else { + state.base.stream << true; + _children[i]->writeDelta(reference->getChild(i), nextState); + } + } + } + } +} + +HeightfieldNode* HeightfieldNode::readSubdivision(HeightfieldStreamState& state) { + if (state.shouldSubdivide()) { + if (!state.shouldSubdivideReference()) { + bool leaf; + state.base.stream >> leaf; + if (leaf) { + return isLeaf() ? this : new HeightfieldNode(_height, _color, _material, _stack); + + } else { + HeightfieldNode* newNode = new HeightfieldNode(_height, _color, _material, _stack); + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + newNode->_children[i] = new HeightfieldNode(); + newNode->_children[i]->readSubdivided(nextState, state, this); + } + return newNode; + } + } else if (!isLeaf()) { + HeightfieldNode* node = this; + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + if (nextState.becameSubdividedOrCollapsed()) { + HeightfieldNode* child = _children[i]->readSubdivision(nextState); + if (_children[i] != child) { + if (node == this) { + node = new HeightfieldNode(*this); + } + node->_children[i] = child; + } + } + } + if (node != this) { + node->mergeChildren(); + } + return node; + } + } else if (!isLeaf()) { + return new HeightfieldNode(_height, _color, _material, _stack); + } + return this; +} + +void HeightfieldNode::writeSubdivision(HeightfieldStreamState& state) const { + if (!state.shouldSubdivide()) { + return; + } + bool leaf = isLeaf(); + if (!state.shouldSubdivideReference()) { + state.base.stream << leaf; + if (!leaf) { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->writeSubdivided(nextState, state, this); + } + } + } else if (!leaf) { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + if (nextState.becameSubdivided()) { + _children[i]->writeSubdivision(nextState); + } + } + } +} + +void HeightfieldNode::readSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, + const HeightfieldNode* ancestor) { + clearChildren(); + + if (!state.shouldSubdivide()) { + // TODO: subdivision encoding + state.base.stream >> _height >> _color >> _material >> _stack; + return; + } + bool leaf; + state.base.stream >> leaf; + if (leaf) { + state.base.stream >> _height >> _color >> _material >> _stack; + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i] = new HeightfieldNode(); + _children[i]->readSubdivided(nextState, ancestorState, ancestor); + } + mergeChildren(); + } +} + +void HeightfieldNode::writeSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, + const HeightfieldNode* ancestor) const { + if (!state.shouldSubdivide()) { + // TODO: subdivision encoding + state.base.stream << _height << _color << _material << _stack; + return; + } + bool leaf = isLeaf(); + state.base.stream << leaf; + if (leaf) { + state.base.stream << _height << _color << _material << _stack; + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->writeSubdivided(nextState, ancestorState, ancestor); + } + } +} + +void HeightfieldNode::clearChildren() { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i].reset(); + } +} + +void HeightfieldNode::mergeChildren(bool height, bool colorMaterial) { + if (isLeaf()) { + return; + } + int heightWidth = 0; + int heightHeight = 0; + int colorWidth = 0; + int colorHeight = 0; + int materialWidth = 0; + int materialHeight = 0; + int stackWidth = 0; + int stackHeight = 0; + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldHeightPointer childHeight = _children[i]->getHeight(); + if (childHeight) { + int childHeightWidth = childHeight->getWidth(); + int childHeightHeight = childHeight->getContents().size() / childHeightWidth; + heightWidth = qMax(heightWidth, childHeightWidth); + heightHeight = qMax(heightHeight, childHeightHeight); + } + HeightfieldColorPointer childColor = _children[i]->getColor(); + if (childColor) { + int childColorWidth = childColor->getWidth(); + int childColorHeight = childColor->getContents().size() / (childColorWidth * DataBlock::COLOR_BYTES); + colorWidth = qMax(colorWidth, childColorWidth); + colorHeight = qMax(colorHeight, childColorHeight); + } + HeightfieldMaterialPointer childMaterial = _children[i]->getMaterial(); + if (childMaterial) { + int childMaterialWidth = childMaterial->getWidth(); + int childMaterialHeight = childMaterial->getContents().size() / childMaterialWidth; + materialWidth = qMax(materialWidth, childMaterialWidth); + materialHeight = qMax(materialHeight, childMaterialHeight); + } + HeightfieldStackPointer childStack = _children[i]->getStack(); + if (childStack) { + int childStackWidth = childStack->getWidth(); + int childStackHeight = childStack->getContents().size() / childStackWidth; + stackWidth = qMax(stackWidth, childStackWidth); + stackHeight = qMax(stackHeight, childStackHeight); + } + } + if (heightWidth > 0 && height) { + QVector heightContents(heightWidth * heightHeight); + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldHeightPointer childHeight = _children[i]->getHeight(); + if (!childHeight) { + continue; + } + int childHeightWidth = childHeight->getWidth(); + int childHeightHeight = childHeight->getContents().size() / childHeightWidth; + if (childHeightWidth != heightWidth || childHeightHeight != heightHeight) { + qWarning() << "Height dimension mismatch [heightWidth=" << heightWidth << ", heightHeight=" << heightHeight << + ", childHeightWidth=" << childHeightWidth << ", childHeightHeight=" << childHeightHeight << "]"; + continue; + } + int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; + int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; + int innerQuadrantHeightWidth = innerHeightWidth / 2; + int innerQuadrantHeightHeight = innerHeightHeight / 2; + int quadrantHeightWidth = innerQuadrantHeightWidth + HeightfieldHeight::HEIGHT_EXTENSION - 1; + int quadrantHeightHeight = innerQuadrantHeightHeight + HeightfieldHeight::HEIGHT_EXTENSION - 1; + quint16* dest = heightContents.data() + (i & Y_MAXIMUM_FLAG ? (innerQuadrantHeightHeight + 1) * heightWidth : 0) + + (i & X_MAXIMUM_FLAG ? innerQuadrantHeightWidth + 1 : 0); + const quint16* src = childHeight->getContents().constData(); + for (int z = 0; z < quadrantHeightHeight; z++, dest += heightWidth, src += heightWidth * 2) { + const quint16* lineSrc = src; + for (quint16* lineDest = dest, *end = dest + quadrantHeightWidth; lineDest != end; lineDest++, lineSrc += 2) { + *lineDest = *lineSrc; + } + } + } + _height = new HeightfieldHeight(heightWidth, heightContents); + + } else if (height) { + _height.reset(); + } + if (colorWidth > 0 && colorMaterial) { + QByteArray colorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF); + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldColorPointer childColor = _children[i]->getColor(); + if (!childColor) { + continue; + } + int childColorWidth = childColor->getWidth(); + int childColorHeight = childColor->getContents().size() / (childColorWidth * DataBlock::COLOR_BYTES); + if (childColorWidth != colorWidth || childColorHeight != colorHeight) { + qWarning() << "Color dimension mismatch [colorWidth=" << colorWidth << ", colorHeight=" << colorHeight << + ", childColorWidth=" << childColorWidth << ", childColorHeight=" << childColorHeight << "]"; + continue; + } + int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; + int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; + int innerQuadrantColorWidth = innerColorWidth / 2; + int innerQuadrantColorHeight = innerColorHeight / 2; + int quadrantColorWidth = innerQuadrantColorWidth + HeightfieldData::SHARED_EDGE; + int quadrantColorHeight = innerQuadrantColorHeight + HeightfieldData::SHARED_EDGE; + char* dest = colorContents.data() + ((i & Y_MAXIMUM_FLAG ? innerQuadrantColorHeight * colorWidth : 0) + + (i & X_MAXIMUM_FLAG ? innerQuadrantColorWidth : 0)) * DataBlock::COLOR_BYTES; + const uchar* src = (const uchar*)childColor->getContents().constData(); + for (int z = 0; z < quadrantColorHeight; z++, dest += colorWidth * DataBlock::COLOR_BYTES, + src += colorWidth * DataBlock::COLOR_BYTES * 2) { + const uchar* lineSrc = src; + for (char* lineDest = dest, *end = dest + quadrantColorWidth * DataBlock::COLOR_BYTES; + lineDest != end; lineDest += DataBlock::COLOR_BYTES, lineSrc += DataBlock::COLOR_BYTES * 2) { + lineDest[0] = lineSrc[0]; + lineDest[1] = lineSrc[1]; + lineDest[2] = lineSrc[2]; + } + } + } + _color = new HeightfieldColor(colorWidth, colorContents); + + } else { + _color.reset(); + } + if (materialWidth > 0 && colorMaterial) { + QByteArray materialContents(materialWidth * materialHeight, 0); + QVector materials; + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldMaterialPointer childMaterial = _children[i]->getMaterial(); + if (!childMaterial) { + continue; + } + int childMaterialWidth = childMaterial->getWidth(); + int childMaterialHeight = childMaterial->getContents().size() / childMaterialWidth; + if (childMaterialWidth != materialWidth || childMaterialHeight != materialHeight) { + qWarning() << "Material dimension mismatch [materialWidth=" << materialWidth << ", materialHeight=" << + materialHeight << ", childMaterialWidth=" << childMaterialWidth << ", childMaterialHeight=" << + childMaterialHeight << "]"; + continue; + } + int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; + int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; + int innerQuadrantMaterialWidth = innerMaterialWidth / 2; + int innerQuadrantMaterialHeight = innerMaterialHeight / 2; + int quadrantMaterialWidth = innerQuadrantMaterialWidth + HeightfieldData::SHARED_EDGE; + int quadrantMaterialHeight = innerQuadrantMaterialHeight + HeightfieldData::SHARED_EDGE; + uchar* dest = (uchar*)materialContents.data() + + (i & Y_MAXIMUM_FLAG ? innerQuadrantMaterialHeight * materialWidth : 0) + + (i & X_MAXIMUM_FLAG ? innerQuadrantMaterialWidth : 0); + const uchar* src = (const uchar*)childMaterial->getContents().constData(); + QHash materialMap; + for (int z = 0; z < quadrantMaterialHeight; z++, dest += materialWidth, src += materialWidth * 2) { + const uchar* lineSrc = src; + for (uchar* lineDest = dest, *end = dest + quadrantMaterialWidth; lineDest != end; lineDest++, lineSrc += 2) { + int value = *lineSrc; + if (value != 0) { + int& mapping = materialMap[value]; + if (mapping == 0) { + mapping = getMaterialIndex(childMaterial->getMaterials().at(value - 1), + materials, materialContents); + } + value = mapping; + } + *lineDest = value; + } + } + } + _material = new HeightfieldMaterial(materialWidth, materialContents, materials); + + } else { + _material.reset(); + } + if (stackWidth > 0) { + QVector stackContents(stackWidth * stackHeight); + QVector stackMaterials; + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldStackPointer childStack = _children[i]->getStack(); + if (!childStack) { + continue; + } + int childStackWidth = childStack->getWidth(); + int childStackHeight = childStack->getContents().size() / childStackWidth; + if (childStackWidth != stackWidth || childStackHeight != stackHeight) { + qWarning() << "Stack dimension mismatch [stackWidth=" << stackWidth << ", stackHeight=" << stackHeight << + ", childStackWidth=" << childStackWidth << ", childStackHeight=" << childStackHeight << "]"; + continue; + } + int innerStackWidth = stackWidth - HeightfieldData::SHARED_EDGE; + int innerStackHeight = stackHeight - HeightfieldData::SHARED_EDGE; + int innerQuadrantStackWidth = innerStackWidth / 2; + int innerQuadrantStackHeight = innerStackHeight / 2; + int quadrantStackWidth = innerQuadrantStackWidth + HeightfieldData::SHARED_EDGE; + int quadrantStackHeight = innerQuadrantStackHeight + HeightfieldData::SHARED_EDGE; + StackArray* dest = stackContents.data() + (i & Y_MAXIMUM_FLAG ? innerQuadrantStackHeight * stackWidth : 0) + + (i & X_MAXIMUM_FLAG ? innerQuadrantStackWidth : 0); + const StackArray* src = childStack->getContents().constData(); + QHash materialMap; + for (int z = 0; z < quadrantStackHeight; z++, dest += stackWidth, src += stackWidth * 2) { + const StackArray* lineSrc = src; + StackArray* lineDest = dest; + for (int x = 0; x < quadrantStackWidth; x++, lineDest++, lineSrc += 2) { + if (lineSrc->isEmpty()) { + continue; + } + int minimumY = lineSrc->getPosition(); + int maximumY = lineSrc->getPosition() + lineSrc->getEntryCount() - 1; + int newMinimumY = minimumY / 2; + int newMaximumY = maximumY / 2; + *lineDest = StackArray(newMaximumY - newMinimumY + 1); + lineDest->setPosition(newMinimumY); + int y = newMinimumY; + for (StackArray::Entry* destEntry = lineDest->getEntryData(), *end = destEntry + lineDest->getEntryCount(); + destEntry != end; destEntry++, y++) { + int srcY = y * 2; + const StackArray::Entry& srcEntry = lineSrc->getEntry(srcY); + destEntry->color = srcEntry.color; + destEntry->material = srcEntry.material; + if (destEntry->material != 0) { + int& mapping = materialMap[destEntry->material]; + if (mapping == 0) { + mapping = getMaterialIndex(childStack->getMaterials().at(destEntry->material - 1), + stackMaterials, stackContents); + } + destEntry->material = mapping; + } + int srcAlpha = qAlpha(srcEntry.color); + glm::vec3 normal; + if (srcAlpha != lineSrc->getEntryAlpha(srcY + 2)) { + const StackArray::Entry& nextSrcEntry = lineSrc->getEntry(srcY + 1); + if (qAlpha(nextSrcEntry.color) == srcAlpha) { + float distance = nextSrcEntry.getHermiteY(normal); + destEntry->setHermiteY(normal, distance * 0.5f + 0.5f); + } else { + float distance = srcEntry.getHermiteY(normal); + destEntry->setHermiteY(normal, distance * 0.5f); + } + } + if (x != quadrantStackWidth - 1 && srcAlpha != lineSrc[2].getEntryAlpha(srcY)) { + const StackArray::Entry& nextSrcEntry = lineSrc[1].getEntry(srcY); + if (qAlpha(nextSrcEntry.color) == srcAlpha) { + float distance = nextSrcEntry.getHermiteX(normal); + destEntry->setHermiteX(normal, distance * 0.5f + 0.5f); + } else { + float distance = srcEntry.getHermiteX(normal); + destEntry->setHermiteX(normal, distance * 0.5f); + } + } + if (z != quadrantStackHeight - 1 && srcAlpha != lineSrc[2 * stackWidth].getEntryAlpha(srcY)) { + const StackArray::Entry& nextSrcEntry = lineSrc[stackWidth].getEntry(srcY); + if (qAlpha(nextSrcEntry.color) == srcAlpha) { + float distance = nextSrcEntry.getHermiteZ(normal); + destEntry->setHermiteZ(normal, distance * 0.5f + 0.5f); + } else { + float distance = srcEntry.getHermiteZ(normal); + destEntry->setHermiteZ(normal, distance * 0.5f); + } + } + } + } + } + } + _stack = new HeightfieldStack(stackWidth, stackContents, stackMaterials); + + } else { + _stack.reset(); + } +} + +QRgb HeightfieldNode::getColorAt(const glm::vec3& location) const { + if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { + return 0; + } + int width = _color->getWidth(); + const QByteArray& contents = _color->getContents(); + const uchar* src = (const uchar*)contents.constData(); + int height = contents.size() / (width * DataBlock::COLOR_BYTES); + int innerWidth = width - HeightfieldData::SHARED_EDGE; + int innerHeight = height - HeightfieldData::SHARED_EDGE; + + glm::vec3 relative = location * glm::vec3((float)innerWidth, 1.0f, (float)innerHeight); + 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* upperLeft = src + (floorZ * width + floorX) * DataBlock::COLOR_BYTES; + const uchar* lowerRight = src + (ceilZ * width + ceilX) * DataBlock::COLOR_BYTES; + glm::vec3 interpolatedColor = glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), + glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z); + + // the final vertex (and thus which triangle we check) depends on which half we're on + if (fracts.x >= fracts.z) { + const uchar* upperRight = src + (floorZ * width + 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]), fracts.z), (fracts.x - fracts.z) / (1.0f - fracts.z)); + + } else { + const uchar* lowerLeft = src + (ceilZ * width + 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]), fracts.z), interpolatedColor, fracts.x / fracts.z); + } + return qRgb(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b); +} + +int HeightfieldNode::getMaterialAt(const glm::vec3& location) const { + if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { + return -1; + } + int width = _material->getWidth(); + const QByteArray& contents = _material->getContents(); + const uchar* src = (const uchar*)contents.constData(); + int height = contents.size() / width; + int innerWidth = width - HeightfieldData::SHARED_EDGE; + int innerHeight = height - HeightfieldData::SHARED_EDGE; + + glm::vec3 relative = location * glm::vec3((float)innerWidth, 1.0f, (float)innerHeight); + return src[(int)glm::round(relative.z) * width + (int)glm::round(relative.x)]; +} + +void HeightfieldNode::maybeRenormalize(const glm::vec3& scale, float normalizeScale, float normalizeOffset, + int innerStackWidth, QVector& heightContents, QVector& stackContents) { + if (normalizeScale == 1.0f && normalizeOffset == 0.0f) { + return; + } + for (quint16* dest = heightContents.data(), *end = dest + heightContents.size(); dest != end; dest++) { + int value = *dest; + if (value != 0) { + *dest = (value + normalizeOffset) * normalizeScale; + } + } + if (stackContents.isEmpty()) { + return; + } + int stackOffset = glm::round(scale.y * normalizeOffset * normalizeScale * innerStackWidth / + (numeric_limits::max() * scale.x)); + for (StackArray* dest = stackContents.data(), *end = dest + stackContents.size(); dest != end; dest++) { + if (!dest->isEmpty()) { + dest->getPositionRef() += stackOffset; + } + } +} + +bool HeightfieldNode::findHeightfieldRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float boundsDistance, float& distance) const { if (!_height) { return false; } @@ -1357,9 +3117,9 @@ bool HeightfieldNode::findRayIntersection(const glm::vec3& origin, const glm::ve int highestX = innerWidth + HeightfieldHeight::HEIGHT_BORDER; int highestZ = innerHeight + HeightfieldHeight::HEIGHT_BORDER; - glm::vec3 scale((float)innerWidth, (float)numeric_limits::max(), (float)innerHeight); - glm::vec3 dir = direction * scale; - glm::vec3 entry = origin * scale + dir * boundsDistance; + glm::vec3 heightScale((float)innerWidth, (float)numeric_limits::max(), (float)innerHeight); + glm::vec3 dir = direction * heightScale; + glm::vec3 entry = origin * heightScale + dir * boundsDistance; entry.x += HeightfieldHeight::HEIGHT_BORDER; entry.z += HeightfieldHeight::HEIGHT_BORDER; @@ -1441,7 +3201,8 @@ bool HeightfieldNode::findRayIntersection(const glm::vec3& origin, const glm::ve } } // 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)) || + if (upperLeft == 0 || upperRight == 0 || lowerLeft == 0 || lowerRight == 0 || + 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; @@ -1486,1022 +3247,18 @@ bool HeightfieldNode::findRayIntersection(const glm::vec3& origin, const glm::ve accumulatedDistance += exitDistance; } - return false; -} - -HeightfieldNode* HeightfieldNode::paintMaterial(const glm::vec3& position, const glm::vec3& radius, - const SharedObjectPointer& material, const QColor& color) { - if (position.x + radius.x < 0.0f || position.z + radius.z < 0.0f || - position.x - radius.x > 1.0f || position.z - radius.z > 1.0f) { - return this; - } - if (!isLeaf()) { - HeightfieldNode* newNode = this; - for (int i = 0; i < CHILD_COUNT; i++) { - HeightfieldNode* newChild = _children[i]->paintMaterial(position * glm::vec3(2.0f, 1.0f, 2.0f) - - glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f), - radius * glm::vec3(2.0f, 1.0f, 2.0f), material, color); - if (_children[i] != newChild) { - if (newNode == this) { - newNode = new HeightfieldNode(*this); - } - newNode->setChild(i, HeightfieldNodePointer(newChild)); - } - } - if (newNode != this) { - newNode->mergeChildren(false, true); - } - return newNode; - } - if (!_height) { - return this; - } - int heightWidth = _height->getWidth(); - int heightHeight = _height->getContents().size() / heightWidth; - int baseWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION + HeightfieldData::SHARED_EDGE; - int baseHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION + HeightfieldData::SHARED_EDGE; - - int colorWidth = baseWidth, colorHeight = baseHeight; - QByteArray colorContents; - if (_color) { - colorWidth = _color->getWidth(); - colorHeight = _color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); - colorContents = _color->getContents(); - - } else { - colorContents = QByteArray(baseWidth * baseHeight * DataBlock::COLOR_BYTES, 0xffU); - } - - int materialWidth = baseWidth, materialHeight = baseHeight; - QByteArray materialContents; - QVector materials; - if (_material) { - materialWidth = _material->getWidth(); - materialHeight = _material->getContents().size() / materialWidth; - materialContents = _material->getContents(); - materials = _material->getMaterials(); - - } else { - materialContents = QByteArray(baseWidth * baseHeight, 0); - } - - int highestX = colorWidth - 1; - int highestZ = colorHeight - 1; - glm::vec3 scale((float)highestX, 1.0f, (float)highestZ); - glm::vec3 center = position * scale; - - glm::vec3 extents = radius * scale; - glm::vec3 start = glm::floor(center - extents); - glm::vec3 end = glm::ceil(center + extents); - - // paint all points within the radius - float z = qMax(start.z, 0.0f); - float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highestX); - int stride = colorWidth * DataBlock::COLOR_BYTES; - uchar* lineDest = (uchar*)colorContents.data() + (int)z * stride + (int)startX * DataBlock::COLOR_BYTES; - float squaredRadius = extents.x * extents.x; - float multiplierZ = extents.x / extents.z; - char red = color.red(), green = color.green(), blue = color.blue(); - bool changed = false; - for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { - uchar* dest = lineDest; - for (float x = startX; x <= endX; x += 1.0f, dest += DataBlock::COLOR_BYTES) { - float dx = x - center.x, dz = (z - center.z) * multiplierZ; - if (dx * dx + dz * dz <= squaredRadius) { - dest[0] = red; - dest[1] = green; - dest[2] = blue; - changed = true; - } - } - lineDest += stride; - } - HeightfieldNode* newNode = this; - if (changed) { - newNode = new HeightfieldNode(*this); - newNode->setColor(HeightfieldColorPointer(new HeightfieldColor(colorWidth, colorContents))); - } - - highestX = materialWidth - 1; - highestZ = materialHeight - 1; - scale = glm::vec3((float)highestX, 1.0f, (float)highestZ); - center = position * scale; - - extents = radius * scale; - start = glm::floor(center - extents); - end = glm::ceil(center + extents); - - // paint all points within the radius - z = qMax(start.z, 0.0f); - startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highestX); - lineDest = (uchar*)materialContents.data() + (int)z * materialWidth + (int)startX; - squaredRadius = extents.x * extents.x; - multiplierZ = extents.x / extents.z; - uchar materialIndex = getMaterialIndex(material, materials, materialContents); - changed = false; - for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { - uchar* dest = lineDest; - for (float x = startX; x <= endX; x += 1.0f, dest++) { - float dx = x - center.x, dz = (z - center.z) * multiplierZ; - if (dx * dx + dz * dz <= squaredRadius) { - *dest = materialIndex; - changed = true; - } - } - lineDest += materialWidth; - } - if (changed) { - if (newNode == this) { - newNode = new HeightfieldNode(*this); - } - clearUnusedMaterials(materials, materialContents); - newNode->setMaterial(HeightfieldMaterialPointer(new HeightfieldMaterial(materialWidth, - materialContents, materials))); - } - - return newNode; -} - -void HeightfieldNode::getRangeAfterHeightPaint(const glm::vec3& position, const glm::vec3& radius, - float height, int& minimum, int& maximum) const { - if (position.x + radius.x < 0.0f || position.z + radius.z < 0.0f || - position.x - radius.x > 1.0f || position.z - radius.z > 1.0f) { - return; - } - if (!isLeaf()) { - for (int i = 0; i < CHILD_COUNT; i++) { - _children[i]->getRangeAfterHeightPaint(position * glm::vec3(2.0f, 1.0f, 2.0f) - - glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f), - radius * glm::vec3(2.0f, 1.0f, 2.0f), height, minimum, maximum); - } - return; - } - if (!_height) { - return; - } - int heightWidth = _height->getWidth(); - int heightHeight = _height->getContents().size() / heightWidth; - QVector contents = _height->getContents(); - int innerWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; - int innerHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; - int highestX = heightWidth - 1; - int highestZ = heightHeight - 1; - - glm::vec3 scale((float)innerWidth, 1.0f, (float)innerHeight); - glm::vec3 center = position * scale; - center.x += 1.0f; - center.z += 1.0f; - - glm::vec3 extents = radius * scale; - glm::vec3 start = glm::floor(center - extents); - glm::vec3 end = glm::ceil(center + extents); - - // first see if we're going to exceed the range limits - float z = qMax(start.z, 0.0f); - float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highestX); - quint16* lineDest = contents.data() + (int)z * heightWidth + (int)startX; - float squaredRadius = extents.x * extents.x; - float squaredRadiusReciprocal = 1.0f / squaredRadius; - float multiplierZ = extents.x / extents.z; - for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { - quint16* dest = lineDest; - for (float x = startX; x <= endX; x += 1.0f, dest++) { - float dx = x - center.x, dz = (z - center.z) * multiplierZ; - float distanceSquared = dx * dx + dz * dz; - if (distanceSquared <= squaredRadius) { - // height falls off towards edges - int value = *dest; - if (value != 0) { - value += height * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; - minimum = qMin(minimum, value); - maximum = qMax(maximum, value); - } - } - } - lineDest += heightWidth; - } -} - -HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& position, const glm::vec3& radius, - float height, float normalizeScale, float normalizeOffset) { - if ((position.x + radius.x < 0.0f || position.z + radius.z < 0.0f || position.x - radius.x > 1.0f || - position.z - radius.z > 1.0f) && normalizeScale == 1.0f && normalizeOffset == 0.0f) { - return this; - } - if (!isLeaf()) { - HeightfieldNode* newNode = this; - for (int i = 0; i < CHILD_COUNT; i++) { - HeightfieldNode* newChild = _children[i]->paintHeight(position * glm::vec3(2.0f, 1.0f, 2.0f) - - glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f), - radius * glm::vec3(2.0f, 1.0f, 2.0f), height, normalizeScale, normalizeOffset); - if (_children[i] != newChild) { - if (newNode == this) { - newNode = new HeightfieldNode(*this); - } - newNode->setChild(i, HeightfieldNodePointer(newChild)); - } - } - if (newNode != this) { - newNode->mergeChildren(true, false); - } - return newNode; - } - if (!_height) { - return this; - } - int heightWidth = _height->getWidth(); - int heightHeight = _height->getContents().size() / heightWidth; - QVector contents = _height->getContents(); - int innerWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; - int innerHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; - int highestX = heightWidth - 1; - int highestZ = heightHeight - 1; - - glm::vec3 scale((float)innerWidth, 1.0f, (float)innerHeight); - glm::vec3 center = position * scale; - center.x += 1.0f; - center.z += 1.0f; - - glm::vec3 extents = radius * scale; - glm::vec3 start = glm::floor(center - extents); - glm::vec3 end = glm::ceil(center + extents); - - // renormalize if necessary - bool changed = false; - if (normalizeScale != 1.0f || normalizeOffset != 0.0f) { - changed = true; - for (quint16* dest = contents.data(), *end = contents.data() + contents.size(); dest != end; dest++) { - int value = *dest; - if (value != 0) { - *dest = (value + normalizeOffset) * normalizeScale; - } - } - } - - // now apply the actual change - float z = qMax(start.z, 0.0f); - float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highestX); - quint16* lineDest = contents.data() + (int)z * heightWidth + (int)startX; - float squaredRadius = extents.x * extents.x; - float squaredRadiusReciprocal = 1.0f / squaredRadius; - float multiplierZ = extents.x / extents.z; - for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { - quint16* dest = lineDest; - for (float x = startX; x <= endX; x += 1.0f, dest++) { - float dx = x - center.x, dz = (z - center.z) * multiplierZ; - float distanceSquared = dx * dx + dz * dz; - if (distanceSquared <= squaredRadius) { - // height falls off towards edges - int value = *dest; - if (value != 0) { - *dest = value + height * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; - changed = true; - } - } - } - lineDest += heightWidth; - } - if (!changed) { - return this; - } - HeightfieldNode* newNode = new HeightfieldNode(*this); - newNode->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, contents))); - return newNode; -} - -HeightfieldNode* HeightfieldNode::clearAndFetchHeight(const glm::vec3& translation, const glm::quat& rotation, - const glm::vec3& scale, const Box& bounds, SharedObjectPointer& heightfield) { - Box nodeBounds = glm::translate(translation) * glm::mat4_cast(rotation) * Box(glm::vec3(), scale); - if (!nodeBounds.intersects(bounds)) { - return this; - } - if (!isLeaf()) { - HeightfieldNode* newNode = this; - for (int i = 0; i < CHILD_COUNT; i++) { - glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); - HeightfieldNode* newChild = _children[i]->clearAndFetchHeight(translation + - rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, - i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, - nextScale, bounds, heightfield); - if (_children[i] != newChild) { - if (newNode == this) { - newNode = new HeightfieldNode(*this); - } - newNode->setChild(i, HeightfieldNodePointer(newChild)); - } - } - if (newNode != this) { - newNode->mergeChildren(); - } - return newNode; - } - if (!_height) { - return this; - } - int heightWidth = _height->getWidth(); - int heightHeight = _height->getContents().size() / heightWidth; - int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; - int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; - float heightIncrementX = scale.x / innerHeightWidth; - float heightIncrementZ = scale.z / innerHeightHeight; - - int colorWidth = heightWidth; - int colorHeight = heightHeight; - if (_color) { - colorWidth = _color->getWidth(); - colorHeight = _color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); - } - int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; - int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; - float colorIncrementX = scale.x / innerColorWidth; - float colorIncrementZ = scale.z / innerColorHeight; - - int materialWidth = colorWidth; - int materialHeight = colorHeight; - if (_material) { - materialWidth = _material->getWidth(); - materialHeight = _material->getContents().size() / materialWidth; - } - int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; - int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; - float materialIncrementX = scale.x / innerMaterialWidth; - float materialIncrementZ = scale.z / innerMaterialHeight; - - float largestIncrementX = qMax(heightIncrementX, qMax(colorIncrementX, materialIncrementX)); - float largestIncrementZ = qMax(heightIncrementZ, qMax(colorIncrementZ, materialIncrementZ)); - - glm::vec3 minimum(glm::floor(bounds.minimum.x / largestIncrementX) * largestIncrementX, nodeBounds.minimum.y, - glm::floor(bounds.minimum.z / largestIncrementZ) * largestIncrementZ); - glm::vec3 maximum(glm::ceil(bounds.maximum.x / largestIncrementX) * largestIncrementX, nodeBounds.maximum.y, - glm::ceil(bounds.maximum.z / largestIncrementZ) * largestIncrementZ); - Box largestBounds(minimum, maximum); - - // enlarge the area to fetch - minimum.x -= largestIncrementX; - maximum.x += largestIncrementX; - minimum.z -= largestIncrementZ; - maximum.z += largestIncrementX; - - glm::mat4 baseTransform = glm::mat4_cast(glm::inverse(rotation)) * glm::translate(-translation); - glm::vec3 inverseScale(innerHeightWidth / scale.x, 1.0f, innerHeightHeight / scale.z); - glm::mat4 transform = glm::scale(inverseScale) * baseTransform; - Box transformedBounds = transform * largestBounds; - - // make sure there are values to clear - int startX = glm::clamp((int)glm::ceil(transformedBounds.minimum.x) + HeightfieldHeight::HEIGHT_BORDER, - 0, heightWidth - 1); - int startZ = glm::clamp((int)glm::ceil(transformedBounds.minimum.z) + HeightfieldHeight::HEIGHT_BORDER, - 0, heightHeight - 1); - int endX = glm::clamp((int)glm::floor(transformedBounds.maximum.x) + HeightfieldHeight::HEIGHT_BORDER, 0, heightWidth - 1); - int endZ = glm::clamp((int)glm::floor(transformedBounds.maximum.z) + HeightfieldHeight::HEIGHT_BORDER, - 0, heightHeight - 1); - const quint16* src = _height->getContents().constData() + startZ * heightWidth + startX; - for (int z = startZ; z <= endZ; z++, src += heightWidth) { - const quint16* lineSrc = src; - for (int x = startX; x <= endX; x++) { - if (*lineSrc++ != 0) { - goto clearableBreak; - } - } - } - return this; - clearableBreak: - - int spannerHeightWidth = (int)((maximum.x - minimum.x) / heightIncrementX) + HeightfieldHeight::HEIGHT_EXTENSION; - int spannerHeightHeight = (int)((maximum.z - minimum.z) / heightIncrementZ) + HeightfieldHeight::HEIGHT_EXTENSION; - int spannerColorWidth = (int)((maximum.x - minimum.x) / colorIncrementX) + HeightfieldData::SHARED_EDGE; - int spannerColorHeight = (int)((maximum.z - minimum.z) / colorIncrementZ) + HeightfieldData::SHARED_EDGE; - int spannerMaterialWidth = (int)((maximum.x - minimum.x) / materialIncrementX) + HeightfieldData::SHARED_EDGE; - int spannerMaterialHeight = (int)((maximum.z - minimum.z) / materialIncrementZ) + HeightfieldData::SHARED_EDGE; - - // create heightfield if necessary - Heightfield* spanner = static_cast(heightfield.data()); - if (!spanner) { - heightfield = spanner = new Heightfield(); - spanner->setTranslation(minimum); - spanner->setScale(maximum.x - minimum.x); - spanner->setAspectY((maximum.y - minimum.y) / spanner->getScale()); - spanner->setAspectZ((maximum.z - minimum.z) / spanner->getScale()); - spanner->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(spannerHeightWidth, - QVector(spannerHeightWidth * spannerHeightHeight)))); - spanner->setColor(HeightfieldColorPointer(new HeightfieldColor(spannerColorWidth, - QByteArray(spannerColorWidth * spannerColorHeight * DataBlock::COLOR_BYTES, 0xffU)))); - spanner->setMaterial(HeightfieldMaterialPointer(new HeightfieldMaterial(spannerMaterialWidth, - QByteArray(spannerMaterialWidth * spannerMaterialHeight, 0), QVector()))); - } - - // fetch the height - glm::vec3 spannerInverseScale((spannerHeightWidth - HeightfieldHeight::HEIGHT_EXTENSION) / spanner->getScale(), 1.0f, - (spannerHeightHeight - HeightfieldHeight::HEIGHT_EXTENSION) / (spanner->getScale() * spanner->getAspectZ())); - glm::mat4 spannerBaseTransform = glm::translate(-spanner->getTranslation()); - glm::mat4 spannerTransform = glm::scale(spannerInverseScale) * spannerBaseTransform; - Box spannerTransformedBounds = spannerTransform * nodeBounds; - int spannerStartX = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.x) + HeightfieldHeight::HEIGHT_BORDER, - 0, spannerHeightWidth - 1); - int spannerStartZ = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.z) + HeightfieldHeight::HEIGHT_BORDER, - 0, spannerHeightHeight - 1); - int spannerEndX = glm::clamp((int)glm::ceil(spannerTransformedBounds.maximum.x) + HeightfieldHeight::HEIGHT_BORDER, - 0, spannerHeightWidth - 1); - int spannerEndZ = glm::clamp((int)glm::ceil(spannerTransformedBounds.maximum.z) + HeightfieldHeight::HEIGHT_BORDER, - 0, spannerHeightHeight - 1); - quint16* dest = spanner->getHeight()->getContents().data() + spannerStartZ * spannerHeightWidth + spannerStartX; - glm::vec3 step = 1.0f / spannerInverseScale; - glm::vec3 initialPosition = glm::inverse(rotation) * (glm::vec3(spannerStartX - HeightfieldHeight::HEIGHT_BORDER, 0, - spannerStartZ - HeightfieldHeight::HEIGHT_BORDER) * step + spanner->getTranslation() - translation) / scale; - glm::vec3 position = initialPosition; - step = glm::inverse(rotation) * step / scale; - float heightScale = numeric_limits::max(); - for (int z = spannerStartZ; z <= spannerEndZ; z++, dest += spannerHeightWidth, position.z += step.z) { - quint16* lineDest = dest; - position.x = initialPosition.x; - for (int x = spannerStartX; x <= spannerEndX; x++, lineDest++, position.x += step.x) { - float height = getHeight(position) * heightScale; - if (height > *lineDest) { - *lineDest = height; - } - } - } - - // and the color - if (_color) { - spannerInverseScale = glm::vec3((spannerColorWidth - HeightfieldData::SHARED_EDGE) / spanner->getScale(), 1.0f, - (spannerColorHeight - HeightfieldData::SHARED_EDGE) / (spanner->getScale() * spanner->getAspectZ())); - spannerTransform = glm::scale(spannerInverseScale) * spannerBaseTransform; - spannerTransformedBounds = spannerTransform * nodeBounds; - spannerStartX = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.x), 0, spannerColorWidth - 1); - spannerStartZ = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.z), 0, spannerColorHeight - 1); - spannerEndX = glm::clamp((int)glm::ceil(spannerTransformedBounds.maximum.x), 0, spannerColorWidth - 1); - spannerEndZ = glm::clamp((int)glm::ceil(spannerTransformedBounds.maximum.z), 0, spannerColorHeight - 1); - - char* dest = spanner->getColor()->getContents().data() + - (spannerStartZ * spannerColorWidth + spannerStartX) * DataBlock::COLOR_BYTES; - step = 1.0f / spannerInverseScale; - initialPosition = glm::inverse(rotation) * (glm::vec3(spannerStartX, 0, spannerStartZ) * step + - spanner->getTranslation() - translation) / scale; - position = initialPosition; - step = glm::inverse(rotation) * step / scale; - for (int z = spannerStartZ; z <= spannerEndZ; z++, dest += spannerColorWidth * DataBlock::COLOR_BYTES, - position.z += step.z) { - char* lineDest = dest; - position.x = initialPosition.x; - for (int x = spannerStartX; x <= spannerEndX; x++, lineDest += DataBlock::COLOR_BYTES, position.x += step.x) { - QRgb color = getColorAt(position); - if (color != 0) { - lineDest[0] = qRed(color); - lineDest[1] = qGreen(color); - lineDest[2] = qBlue(color); - } - } - } - } - - // and the material - if (_material) { - spannerInverseScale = glm::vec3((spannerMaterialWidth - HeightfieldData::SHARED_EDGE) / spanner->getScale(), 1.0f, - (spannerMaterialHeight - HeightfieldData::SHARED_EDGE) / (spanner->getScale() * spanner->getAspectZ())); - spannerTransform = glm::scale(spannerInverseScale) * spannerBaseTransform; - spannerTransformedBounds = spannerTransform * nodeBounds; - spannerStartX = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.x), 0, spannerMaterialWidth - 1); - spannerStartZ = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.z), 0, spannerMaterialHeight - 1); - spannerEndX = glm::clamp((int)glm::ceil(spannerTransformedBounds.maximum.x), 0, spannerMaterialWidth - 1); - spannerEndZ = glm::clamp((int)glm::ceil(spannerTransformedBounds.maximum.z), 0, spannerMaterialHeight - 1); - - char* dest = spanner->getMaterial()->getContents().data() + spannerStartZ * spannerMaterialWidth + spannerStartX; - step = 1.0f / spannerInverseScale; - initialPosition = glm::inverse(rotation) * (glm::vec3(spannerStartX, 0, spannerStartZ) * step + - spanner->getTranslation() - translation) / scale; - position = initialPosition; - step = glm::inverse(rotation) * step / scale; - QHash materialMap; - for (int z = spannerStartZ; z <= spannerEndZ; z++, dest += spannerMaterialWidth, position.z += step.z) { - char* lineDest = dest; - position.x = initialPosition.x; - for (int x = spannerStartX; x <= spannerEndX; x++, lineDest++, position.x += step.x) { - int material = getMaterialAt(position); - if (material != -1) { - if (material != 0) { - int& mapping = materialMap[material]; - if (mapping == 0) { - material = mapping = getMaterialIndex(_material->getMaterials().at(material - 1), - spanner->getMaterial()->getMaterials(), spanner->getMaterial()->getContents()); - } - } - *lineDest = material; - } - } - } - } - - // clear the height - QVector newHeightContents = _height->getContents(); - dest = newHeightContents.data() + startZ * heightWidth + startX; - for (int z = startZ; z <= endZ; z++, dest += heightWidth) { - memset(dest, 0, (endX - startX + 1) * sizeof(quint16)); - } - - HeightfieldNode* newNode = new HeightfieldNode(); - newNode->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents))); - - // and the color - if (_color) { - inverseScale = glm::vec3(innerColorWidth / scale.x, 1.0f, innerColorHeight / scale.z); - transform = glm::scale(inverseScale) * baseTransform; - transformedBounds = transform * largestBounds; - startX = glm::clamp((int)glm::ceil(transformedBounds.minimum.x), 0, colorWidth - 1); - startZ = glm::clamp((int)glm::ceil(transformedBounds.minimum.z), 0, colorHeight - 1); - endX = glm::clamp((int)glm::floor(transformedBounds.maximum.x), 0, colorWidth - 1); - endZ = glm::clamp((int)glm::floor(transformedBounds.maximum.z), 0, colorHeight - 1); - QByteArray newColorContents = _color->getContents(); - char* dest = newColorContents.data() + (startZ * colorWidth + startX) * DataBlock::COLOR_BYTES; - for (int z = startZ; z <= endZ; z++, dest += colorWidth * DataBlock::COLOR_BYTES) { - memset(dest, 0, (endX - startX + 1) * DataBlock::COLOR_BYTES); - } - newNode->setColor(HeightfieldColorPointer(new HeightfieldColor(colorWidth, newColorContents))); - } - - // and the material - if (_material) { - inverseScale = glm::vec3(innerMaterialWidth / scale.x, 1.0f, innerMaterialHeight / scale.z); - transform = glm::scale(inverseScale) * baseTransform; - transformedBounds = transform * largestBounds; - startX = glm::clamp((int)glm::ceil(transformedBounds.minimum.x), 0, materialWidth - 1); - startZ = glm::clamp((int)glm::ceil(transformedBounds.minimum.z), 0, materialHeight - 1); - endX = glm::clamp((int)glm::floor(transformedBounds.maximum.x), 0, materialWidth - 1); - endZ = glm::clamp((int)glm::floor(transformedBounds.maximum.z), 0, materialHeight - 1); - QByteArray newMaterialContents = _material->getContents(); - QVector newMaterials = _material->getMaterials(); - char* dest = newMaterialContents.data() + startZ * materialWidth + startX; - for (int z = startZ; z <= endZ; z++, dest += materialWidth) { - memset(dest, 0, endX - startX + 1); - } - clearUnusedMaterials(newMaterials, newMaterialContents); - newNode->setMaterial(HeightfieldMaterialPointer(new HeightfieldMaterial( - materialWidth, newMaterialContents, newMaterials))); - } - - return newNode; -} - -void HeightfieldNode::read(HeightfieldStreamState& state) { - clearChildren(); - - if (!state.shouldSubdivide()) { - state.base.stream >> _height >> _color >> _material; - return; - } - bool leaf; - state.base.stream >> leaf; - if (leaf) { - state.base.stream >> _height >> _color >> _material; - - } else { - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i] = new HeightfieldNode(); - _children[i]->read(nextState); - } - mergeChildren(); - } -} - -void HeightfieldNode::write(HeightfieldStreamState& state) const { - if (!state.shouldSubdivide()) { - state.base.stream << _height << _color << _material; - return; - } - bool leaf = isLeaf(); - state.base.stream << leaf; - if (leaf) { - state.base.stream << _height << _color << _material; - - } else { - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i]->write(nextState); - } - } -} - -void HeightfieldNode::readDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) { - clearChildren(); - - if (!state.shouldSubdivide()) { - state.base.stream.readDelta(_height, reference->getHeight()); - state.base.stream.readDelta(_color, reference->getColor()); - state.base.stream.readDelta(_material, reference->getMaterial()); - return; - } - bool leaf; - state.base.stream >> leaf; - if (leaf) { - state.base.stream.readDelta(_height, reference->getHeight()); - state.base.stream.readDelta(_color, reference->getColor()); - state.base.stream.readDelta(_material, reference->getMaterial()); - - } else { - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - if (reference->isLeaf() || !state.shouldSubdivideReference()) { - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i] = new HeightfieldNode(); - _children[i]->read(nextState); - } - } else { - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - bool changed; - state.base.stream >> changed; - if (changed) { - _children[i] = new HeightfieldNode(); - _children[i]->readDelta(reference->getChild(i), nextState); - } else { - if (nextState.becameSubdividedOrCollapsed()) { - _children[i] = reference->getChild(i)->readSubdivision(nextState); - - } else { - _children[i] = reference->getChild(i); - } - } - } - } - mergeChildren(); - } -} - -void HeightfieldNode::writeDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) const { - if (!state.shouldSubdivide()) { - state.base.stream.writeDelta(_height, reference->getHeight()); - state.base.stream.writeDelta(_color, reference->getColor()); - state.base.stream.writeDelta(_material, reference->getMaterial()); - return; - } - bool leaf = isLeaf(); - state.base.stream << leaf; - if (leaf) { - state.base.stream.writeDelta(_height, reference->getHeight()); - state.base.stream.writeDelta(_color, reference->getColor()); - state.base.stream.writeDelta(_material, reference->getMaterial()); - - } else { - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - if (reference->isLeaf() || !state.shouldSubdivideReference()) { - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i]->write(nextState); - } - } else { - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - if (_children[i] == reference->getChild(i)) { - state.base.stream << false; - if (nextState.becameSubdivided()) { - _children[i]->writeSubdivision(nextState); - } - } else { - state.base.stream << true; - _children[i]->writeDelta(reference->getChild(i), nextState); - } - } - } - } -} - -HeightfieldNode* HeightfieldNode::readSubdivision(HeightfieldStreamState& state) { - if (state.shouldSubdivide()) { - if (!state.shouldSubdivideReference()) { - bool leaf; - state.base.stream >> leaf; - if (leaf) { - return isLeaf() ? this : new HeightfieldNode(_height, _color, _material); - - } else { - HeightfieldNode* newNode = new HeightfieldNode(_height, _color, _material); - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - newNode->_children[i] = new HeightfieldNode(); - newNode->_children[i]->readSubdivided(nextState, state, this); - } - return newNode; - } - } else if (!isLeaf()) { - HeightfieldNode* node = this; - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - if (nextState.becameSubdividedOrCollapsed()) { - HeightfieldNode* child = _children[i]->readSubdivision(nextState); - if (_children[i] != child) { - if (node == this) { - node = new HeightfieldNode(*this); - } - node->_children[i] = child; - } - } - } - if (node != this) { - node->mergeChildren(); - } - return node; - } - } else if (!isLeaf()) { - return new HeightfieldNode(_height, _color, _material); - } - return this; -} - -void HeightfieldNode::writeSubdivision(HeightfieldStreamState& state) const { - if (!state.shouldSubdivide()) { - return; - } - bool leaf = isLeaf(); - if (!state.shouldSubdivideReference()) { - state.base.stream << leaf; - if (!leaf) { - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i]->writeSubdivided(nextState, state, this); - } - } - } else if (!leaf) { - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - if (nextState.becameSubdivided()) { - _children[i]->writeSubdivision(nextState); - } - } - } -} - -void HeightfieldNode::readSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, - const HeightfieldNode* ancestor) { - clearChildren(); - - if (!state.shouldSubdivide()) { - // TODO: subdivision encoding - state.base.stream >> _height >> _color >> _material; - return; - } - bool leaf; - state.base.stream >> leaf; - if (leaf) { - state.base.stream >> _height >> _color >> _material; - - } else { - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i] = new HeightfieldNode(); - _children[i]->readSubdivided(nextState, ancestorState, ancestor); - } - mergeChildren(); - } -} - -void HeightfieldNode::writeSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, - const HeightfieldNode* ancestor) const { - if (!state.shouldSubdivide()) { - // TODO: subdivision encoding - state.base.stream << _height << _color << _material; - return; - } - bool leaf = isLeaf(); - state.base.stream << leaf; - if (leaf) { - state.base.stream << _height << _color << _material; - - } else { - HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; - for (int i = 0; i < CHILD_COUNT; i++) { - nextState.setMinimum(state.minimum, i); - _children[i]->writeSubdivided(nextState, ancestorState, ancestor); - } - } -} - -void HeightfieldNode::clearChildren() { - for (int i = 0; i < CHILD_COUNT; i++) { - _children[i].reset(); - } -} - -void HeightfieldNode::mergeChildren(bool height, bool colorMaterial) { - if (isLeaf()) { - return; - } - int heightWidth = 0; - int heightHeight = 0; - int colorWidth = 0; - int colorHeight = 0; - int materialWidth = 0; - int materialHeight = 0; - for (int i = 0; i < CHILD_COUNT; i++) { - HeightfieldHeightPointer childHeight = _children[i]->getHeight(); - if (childHeight) { - int childHeightWidth = childHeight->getWidth(); - int childHeightHeight = childHeight->getContents().size() / childHeightWidth; - heightWidth = qMax(heightWidth, childHeightWidth); - heightHeight = qMax(heightHeight, childHeightHeight); - } - HeightfieldColorPointer childColor = _children[i]->getColor(); - if (childColor) { - int childColorWidth = childColor->getWidth(); - int childColorHeight = childColor->getContents().size() / (childColorWidth * DataBlock::COLOR_BYTES); - colorWidth = qMax(colorWidth, childColorWidth); - colorHeight = qMax(colorHeight, childColorHeight); - } - HeightfieldMaterialPointer childMaterial = _children[i]->getMaterial(); - if (childMaterial) { - int childMaterialWidth = childMaterial->getWidth(); - int childMaterialHeight = childMaterial->getContents().size() / childMaterialWidth; - materialWidth = qMax(materialWidth, childMaterialWidth); - materialHeight = qMax(materialHeight, childMaterialHeight); - } - } - if (heightWidth > 0 && height) { - QVector heightContents(heightWidth * heightHeight); - for (int i = 0; i < CHILD_COUNT; i++) { - HeightfieldHeightPointer childHeight = _children[i]->getHeight(); - int childHeightWidth = childHeight->getWidth(); - int childHeightHeight = childHeight->getContents().size() / childHeightWidth; - if (childHeightWidth != heightWidth || childHeightHeight != heightHeight) { - qWarning() << "Height dimension mismatch [heightWidth=" << heightWidth << ", heightHeight=" << heightHeight << - ", childHeightWidth=" << childHeightWidth << ", childHeightHeight=" << childHeightHeight << "]"; - continue; - } - int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; - int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; - int innerQuadrantHeightWidth = innerHeightWidth / 2; - int innerQuadrantHeightHeight = innerHeightHeight / 2; - int quadrantHeightWidth = innerQuadrantHeightWidth + HeightfieldHeight::HEIGHT_EXTENSION - 1; - int quadrantHeightHeight = innerQuadrantHeightHeight + HeightfieldHeight::HEIGHT_EXTENSION - 1; - quint16* dest = heightContents.data() + (i & Y_MAXIMUM_FLAG ? (innerQuadrantHeightHeight + 1) * heightWidth : 0) + - (i & X_MAXIMUM_FLAG ? innerQuadrantHeightWidth + 1 : 0); - const quint16* src = childHeight->getContents().constData(); - for (int z = 0; z < quadrantHeightHeight; z++, dest += heightWidth, src += heightWidth * 2) { - const quint16* lineSrc = src; - for (quint16* lineDest = dest, *end = dest + quadrantHeightWidth; lineDest != end; lineDest++, lineSrc += 2) { - *lineDest = *lineSrc; - } - } - } - _height = new HeightfieldHeight(heightWidth, heightContents); - - } else if (height) { - _height.reset(); - } - if (!colorMaterial) { - return; - } - if (colorWidth > 0) { - QByteArray colorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xffU); - for (int i = 0; i < CHILD_COUNT; i++) { - HeightfieldColorPointer childColor = _children[i]->getColor(); - if (!childColor) { - continue; - } - int childColorWidth = childColor->getWidth(); - int childColorHeight = childColor->getContents().size() / (childColorWidth * DataBlock::COLOR_BYTES); - if (childColorWidth != colorWidth || childColorHeight != colorHeight) { - qWarning() << "Color dimension mismatch [colorWidth=" << colorWidth << ", colorHeight=" << colorHeight << - ", childColorWidth=" << childColorWidth << ", childColorHeight=" << childColorHeight << "]"; - continue; - } - int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; - int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; - int innerQuadrantColorWidth = innerColorWidth / 2; - int innerQuadrantColorHeight = innerColorHeight / 2; - int quadrantColorWidth = innerQuadrantColorWidth + HeightfieldData::SHARED_EDGE; - int quadrantColorHeight = innerQuadrantColorHeight + HeightfieldData::SHARED_EDGE; - char* dest = colorContents.data() + ((i & Y_MAXIMUM_FLAG ? innerQuadrantColorHeight * colorWidth : 0) + - (i & X_MAXIMUM_FLAG ? innerQuadrantColorWidth : 0)) * DataBlock::COLOR_BYTES; - const uchar* src = (const uchar*)childColor->getContents().constData(); - for (int z = 0; z < quadrantColorHeight; z++, dest += colorWidth * DataBlock::COLOR_BYTES, - src += colorWidth * DataBlock::COLOR_BYTES * 2) { - const uchar* lineSrc = src; - for (char* lineDest = dest, *end = dest + quadrantColorWidth * DataBlock::COLOR_BYTES; - lineDest != end; lineDest += DataBlock::COLOR_BYTES, lineSrc += DataBlock::COLOR_BYTES * 2) { - lineDest[0] = lineSrc[0]; - lineDest[1] = lineSrc[1]; - lineDest[2] = lineSrc[2]; - } - } - } - _color = new HeightfieldColor(colorWidth, colorContents); - - } else { - _color.reset(); - } - if (materialWidth > 0) { - QByteArray materialContents(materialWidth * materialHeight, 0); - QVector materials; - for (int i = 0; i < CHILD_COUNT; i++) { - HeightfieldMaterialPointer childMaterial = _children[i]->getMaterial(); - if (!childMaterial) { - continue; - } - int childMaterialWidth = childMaterial->getWidth(); - int childMaterialHeight = childMaterial->getContents().size() / childMaterialWidth; - if (childMaterialWidth != materialWidth || childMaterialHeight != materialHeight) { - qWarning() << "Material dimension mismatch [materialWidth=" << materialWidth << ", materialHeight=" << - materialHeight << ", childMaterialWidth=" << childMaterialWidth << ", childMaterialHeight=" << - childMaterialHeight << "]"; - continue; - } - int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; - int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; - int innerQuadrantMaterialWidth = innerMaterialWidth / 2; - int innerQuadrantMaterialHeight = innerMaterialHeight / 2; - int quadrantMaterialWidth = innerQuadrantMaterialWidth + HeightfieldData::SHARED_EDGE; - int quadrantMaterialHeight = innerQuadrantMaterialHeight + HeightfieldData::SHARED_EDGE; - uchar* dest = (uchar*)materialContents.data() + - (i & Y_MAXIMUM_FLAG ? innerQuadrantMaterialHeight * materialWidth : 0) + - (i & X_MAXIMUM_FLAG ? innerQuadrantMaterialWidth : 0); - const uchar* src = (const uchar*)childMaterial->getContents().constData(); - QHash materialMap; - for (int z = 0; z < quadrantMaterialHeight; z++, dest += materialWidth, src += materialWidth * 2) { - const uchar* lineSrc = src; - for (uchar* lineDest = dest, *end = dest + quadrantMaterialWidth; lineDest != end; lineDest++, lineSrc += 2) { - int value = *lineSrc; - if (value != 0) { - int& mapping = materialMap[value]; - if (mapping == 0) { - mapping = getMaterialIndex(childMaterial->getMaterials().at(value - 1), - materials, materialContents); - } - value = mapping; - } - *lineDest = value; - } - } - } - _material = new HeightfieldMaterial(materialWidth, materialContents, materials); - - } else { - _material.reset(); - } -} - -QRgb HeightfieldNode::getColorAt(const glm::vec3& location) const { - if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { - return 0; - } - int width = _color->getWidth(); - const QByteArray& contents = _color->getContents(); - const uchar* src = (const uchar*)contents.constData(); - int height = contents.size() / (width * DataBlock::COLOR_BYTES); - int innerWidth = width - HeightfieldData::SHARED_EDGE; - int innerHeight = height - HeightfieldData::SHARED_EDGE; - - glm::vec3 relative = location * glm::vec3((float)innerWidth, 1.0f, (float)innerHeight); - 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* upperLeft = src + (floorZ * width + floorX) * DataBlock::COLOR_BYTES; - const uchar* lowerRight = src + (ceilZ * width + ceilX) * DataBlock::COLOR_BYTES; - glm::vec3 interpolatedColor = glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), - glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z); - - // the final vertex (and thus which triangle we check) depends on which half we're on - if (fracts.x >= fracts.z) { - const uchar* upperRight = src + (floorZ * width + 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]), fracts.z), (fracts.x - fracts.z) / (1.0f - fracts.z)); - - } else { - const uchar* lowerLeft = src + (ceilZ * width + 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]), fracts.z), interpolatedColor, fracts.x / fracts.z); - } - return qRgb(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b); -} - -int HeightfieldNode::getMaterialAt(const glm::vec3& location) const { - if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { - return -1; - } - int width = _material->getWidth(); - const QByteArray& contents = _material->getContents(); - const uchar* src = (const uchar*)contents.constData(); - int height = contents.size() / width; - int innerWidth = width - HeightfieldData::SHARED_EDGE; - int innerHeight = height - HeightfieldData::SHARED_EDGE; - - glm::vec3 relative = location * glm::vec3((float)innerWidth, 1.0f, (float)innerHeight); - return src[(int)glm::round(relative.z) * width + (int)glm::round(relative.x)]; + return false; } AbstractHeightfieldNodeRenderer::~AbstractHeightfieldNodeRenderer() { } +bool AbstractHeightfieldNodeRenderer::findRayIntersection(const glm::vec3& translation, + const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& origin, const glm::vec3& direction, + float boundsDistance, float& distance) const { + return false; +} + Heightfield::Heightfield() : _aspectY(1.0f), _aspectZ(1.0f) { @@ -2516,6 +3273,7 @@ Heightfield::Heightfield() : connect(this, &Heightfield::heightChanged, this, &Heightfield::updateRoot); connect(this, &Heightfield::colorChanged, this, &Heightfield::updateRoot); connect(this, &Heightfield::materialChanged, this, &Heightfield::updateRoot); + connect(this, &Heightfield::stackChanged, this, &Heightfield::updateRoot); updateRoot(); } @@ -2549,9 +3307,9 @@ void Heightfield::setMaterial(const HeightfieldMaterialPointer& material) { } } -void Heightfield::setRoot(const HeightfieldNodePointer& root) { - if (_root != root) { - emit rootChanged(_root = root); +void Heightfield::setStack(const HeightfieldStackPointer& stack) { + if (_stack != stack) { + emit stackChanged(_stack = stack); } } @@ -2578,66 +3336,66 @@ bool Heightfield::isHeightfield() const { } float Heightfield::getHeight(const glm::vec3& location) const { - float result = _root->getHeight(glm::inverse(getRotation()) * (location - getTranslation()) * glm::vec3(1.0f / getScale(), - 0.0f, 1.0f / (getScale() * _aspectZ))); - return (result == -FLT_MAX) ? -FLT_MAX : (getTranslation().y + result * getScale() * _aspectY); + float distance; + glm::vec3 down = getRotation() * glm::vec3(0.0f, -1.0f, 0.0f); + glm::vec3 origin = location - down * (glm::dot(down, location) + getScale() * _aspectY - glm::dot(down, getTranslation())); + if (findRayIntersection(origin, down, distance)) { + return origin.y + distance * down.y; + } + return -FLT_MAX; } bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { - glm::quat inverseRotation = glm::inverse(getRotation()); - glm::vec3 inverseScale(1.0f / getScale(), 1.0f / (getScale() * _aspectY), 1.0f / (getScale() * _aspectZ)); - return _root->findRayIntersection(inverseRotation * (origin - getTranslation()) * inverseScale, - inverseRotation * direction * inverseScale, distance); + return _root->findRayIntersection(getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * _aspectY, + getScale() * _aspectZ), origin, direction, distance); } -Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, - const SharedObjectPointer& material, const QColor& color) { - glm::vec3 inverseScale(1.0f / getScale(), 1.0f, 1.0f / (getScale() * _aspectZ)); - HeightfieldNode* newRoot = _root->paintMaterial(glm::inverse(getRotation()) * (position - getTranslation()) * - inverseScale, radius * inverseScale, material, color); - if (_root == newRoot) { - return this; - } - Heightfield* newHeightfield = static_cast(clone(true)); - newHeightfield->setRoot(HeightfieldNodePointer(newRoot)); - return newHeightfield; -} - -Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height) { +Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase) { // first see if we're going to exceed the range limits - glm::vec3 inverseScale(1.0f / getScale(), 1.0f, 1.0f / (getScale() * _aspectZ)); - glm::vec3 relativePosition = glm::inverse(getRotation()) * (position - getTranslation()) * inverseScale; - glm::vec3 relativeRadius = radius * inverseScale; - int minimumValue = 1, maximumValue = numeric_limits::max(); - _root->getRangeAfterHeightPaint(relativePosition, relativeRadius, - height * numeric_limits::max() / (getScale() * _aspectY), minimumValue, maximumValue); - - // renormalize if necessary - Heightfield* newHeightfield = static_cast(clone(true)); - float normalizeScale = 1.0f, normalizeOffset = 0.0f; - if (minimumValue < 1 || maximumValue > numeric_limits::max()) { - normalizeScale = (numeric_limits::max() - 1.0f) / (maximumValue - minimumValue); - normalizeOffset = 1.0f - minimumValue; - newHeightfield->setAspectY(_aspectY / normalizeScale); - newHeightfield->setTranslation(getTranslation() - getRotation() * - glm::vec3(0.0f, normalizeOffset * _aspectY * getScale() / (numeric_limits::max() - 1), 0.0f)); + float minimumValue = 1.0f, maximumValue = numeric_limits::max(); + if (set) { + float heightValue = height * numeric_limits::max() / (getScale() * _aspectY); + minimumValue = qMin(minimumValue, heightValue); + maximumValue = qMax(maximumValue, heightValue); + + } else if (!erase) { + _root->getRangeAfterHeightPaint(getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * _aspectY, + getScale() * _aspectZ), position, radius, height, minimumValue, maximumValue); } - // now apply the actual change - newHeightfield->setRoot(HeightfieldNodePointer(_root->paintHeight(relativePosition, relativeRadius, - height * numeric_limits::max() / (getScale() * newHeightfield->getAspectY()), - normalizeScale, normalizeOffset))); + // normalize if necessary + float normalizeScale, normalizeOffset; + Heightfield* newHeightfield = prepareEdit(minimumValue, maximumValue, normalizeScale, normalizeOffset); + newHeightfield->setRoot(HeightfieldNodePointer(_root->paintHeight(newHeightfield->getTranslation(), getRotation(), + glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), position, radius, height, + set, erase, normalizeScale, normalizeOffset))); return newHeightfield; } -Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer& heightfield) { - HeightfieldNode* newRoot = _root->clearAndFetchHeight(getTranslation(), getRotation(), - glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), bounds, heightfield); - if (_root == newRoot) { - return this; - } +Spanner* Heightfield::fillHeight(const glm::vec3& position, float radius) { Heightfield* newHeightfield = static_cast(clone(true)); - newHeightfield->setRoot(HeightfieldNodePointer(newRoot)); + newHeightfield->setRoot(HeightfieldNodePointer(_root->fillHeight(getTranslation(), getRotation(), + glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), position, radius))); + return newHeightfield; +} + +Spanner* Heightfield::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, + const QColor& color, bool paint, bool voxelize) { + // first see if we're going to exceed the range limits, normalizing if necessary + Spanner* spannerData = static_cast(spanner.data()); + float normalizeScale = 1.0f, normalizeOffset = 0.0f; + Heightfield* newHeightfield; + if (paint) { + newHeightfield = static_cast(clone(true)); + } else { + float minimumValue = 1.0f, maximumValue = numeric_limits::max(); + _root->getRangeAfterEdit(getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * _aspectY, + getScale() * _aspectZ), spannerData->getBounds(), minimumValue, maximumValue); + newHeightfield = prepareEdit(minimumValue, maximumValue, normalizeScale, normalizeOffset); + } + newHeightfield->setRoot(HeightfieldNodePointer(_root->setMaterial(newHeightfield->getTranslation(), getRotation(), + glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), spannerData, + material, color, paint, voxelize, normalizeScale, normalizeOffset))); return newHeightfield; } @@ -2948,7 +3706,7 @@ bool Heightfield::intersects(const glm::vec3& start, const glm::vec3& end, float void Heightfield::writeExtra(Bitstream& out) const { if (getWillBeVoxelized()) { - out << _height << _color << _material; + out << _height << _color << _material << _stack; return; } MetavoxelLOD lod; @@ -2962,7 +3720,7 @@ void Heightfield::writeExtra(Bitstream& out) const { void Heightfield::readExtra(Bitstream& in) { if (getWillBeVoxelized()) { - in >> _height >> _color >> _material; + in >> _height >> _color >> _material >> _stack; return; } MetavoxelLOD lod; @@ -3008,6 +3766,7 @@ void Heightfield::readExtraDelta(Bitstream& in, const SharedObject* reference) { HeightfieldStreamBase base = { in, lod, referenceLOD }; HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; + setRoot(static_cast(reference)->getRoot()); bool changed; in >> changed; if (changed) { @@ -3072,7 +3831,24 @@ void Heightfield::updateBounds() { void Heightfield::updateRoot() { HeightfieldNodePointer root(new HeightfieldNode()); if (_height) { - root->setContents(_height, _color, _material); + root->setContents(_height, _color, _material, _stack); } setRoot(root); } + +Heightfield* Heightfield::prepareEdit(float minimumValue, float maximumValue, float& normalizeScale, float& normalizeOffset) { + // renormalize if necessary + Heightfield* newHeightfield = static_cast(clone(true)); + if (minimumValue < 1.0f || maximumValue > numeric_limits::max()) { + normalizeScale = (numeric_limits::max() - 1.0f) / (maximumValue - minimumValue); + normalizeOffset = 1.0f - minimumValue; + newHeightfield->setAspectY(_aspectY / normalizeScale); + newHeightfield->setTranslation(getTranslation() - getRotation() * + glm::vec3(0.0f, normalizeOffset * _aspectY * getScale() / (numeric_limits::max() - 1), 0.0f)); + } else { + normalizeScale = 1.0f; + normalizeOffset = 0.0f; + } + return newHeightfield; +} + diff --git a/libraries/metavoxels/src/Spanner.h b/libraries/metavoxels/src/Spanner.h index bec1355b48..12d04edd14 100644 --- a/libraries/metavoxels/src/Spanner.h +++ b/libraries/metavoxels/src/Spanner.h @@ -18,11 +18,13 @@ #include "MetavoxelUtil.h" class AbstractHeightfieldNodeRenderer; +class DataBlock; class Heightfield; class HeightfieldColor; class HeightfieldHeight; class HeightfieldMaterial; class HeightfieldNode; +class HeightfieldStack; class SpannerRenderer; /// An object that spans multiple octree cells. @@ -71,19 +73,20 @@ public: /// Finds the intersection between the described ray and this spanner. virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - /// Attempts to paint on the spanner. - /// \return the modified spanner, or this if no modification was performed - virtual Spanner* paintMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material, - const QColor& color); - /// Attempts to modify the spanner's height. + /// \param set whether to set the height as opposed to raising/lowering it + /// \param erase whether to erase height values /// \return the modified spanner, or this if no modification was performed - virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height); + virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase); - /// Attempts to clear and fetch part of the spanner's height. - /// \param heightfield the heightfield to populate + /// Attempts to fill the spanner's height (adding removing volumetric information). /// \return the modified spanner, or this if no modification was performed - virtual Spanner* clearAndFetchHeight(const Box& bounds, SharedObjectPointer& heightfield); + virtual Spanner* fillHeight(const glm::vec3& position, float radius); + + /// Attempts to "sculpt" or "paint," etc., with the supplied spanner. + /// \return the modified spanner, or this if no modification was performed + virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, + const QColor& color, bool paint, bool voxelize); /// Checks whether this spanner has its own colors. virtual bool hasOwnColors() const; @@ -292,6 +295,42 @@ private: QUrl _url; }; +typedef QExplicitlySharedDataPointer DataBlockPointer; + +/// Base class for blocks of data. +class DataBlock : public QSharedData { +public: + + static const int COLOR_BYTES = 3; + + virtual ~DataBlock(); + + void setDeltaData(const DataBlockPointer& deltaData) { _deltaData = deltaData; } + const DataBlockPointer& getDeltaData() const { return _deltaData; } + + void setEncodedDelta(const QByteArray& encodedDelta) { _encodedDelta = encodedDelta; } + const QByteArray& getEncodedDelta() const { return _encodedDelta; } + + QMutex& getEncodedDeltaMutex() { return _encodedDeltaMutex; } + +protected: + + QByteArray _encoded; + QMutex _encodedMutex; + + DataBlockPointer _deltaData; + QByteArray _encodedDelta; + QMutex _encodedDeltaMutex; + + class EncodedSubdivision { + public: + DataBlockPointer ancestor; + QByteArray data; + }; + QVector _encodedSubdivisions; + QMutex _encodedSubdivisionsMutex; +}; + /// Base class for heightfield data blocks. class HeightfieldData : public DataBlock { public: @@ -466,6 +505,126 @@ Bitstream& operator>>(Bitstream& in, HeightfieldMaterialPointer& value); template<> void Bitstream::writeRawDelta(const HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference); template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference); +/// Contains the description of a material. +class MaterialObject : public SharedObject { + Q_OBJECT + Q_PROPERTY(QUrl diffuse MEMBER _diffuse) + Q_PROPERTY(float scaleS MEMBER _scaleS) + Q_PROPERTY(float scaleT MEMBER _scaleT) + +public: + + Q_INVOKABLE MaterialObject(); + + const QUrl& getDiffuse() const { return _diffuse; } + + float getScaleS() const { return _scaleS; } + float getScaleT() const { return _scaleT; } + +private: + + QUrl _diffuse; + float _scaleS; + float _scaleT; +}; + +/// Finds a material index for the supplied material in the provided list, adding an entry if necessary. Returns -1 +/// on failure (no room to add new material). +int getMaterialIndex(const SharedObjectPointer& material, QVector& materials); + +typedef QExplicitlySharedDataPointer HeightfieldStackPointer; + +/// A single column within a stack block. +class StackArray : public QByteArray { +public: + +#pragma pack(push, 1) + /// A single entry within the array. + class Entry { + public: + quint32 color; + uchar material; + quint32 hermiteX; + quint32 hermiteY; + quint32 hermiteZ; + + Entry(); + + bool isSet() const { return qAlpha(color) != 0; } + + bool isZero() const; + bool isMergeable(const Entry& other) const; + + void setHermiteX(const glm::vec3& normal, float position); + float getHermiteX(glm::vec3& normal) const; + + void setHermiteY(const glm::vec3& normal, float position); + float getHermiteY(glm::vec3& normal) const; + + void setHermiteZ(const glm::vec3& normal, float position); + float getHermiteZ(glm::vec3& normal) const; + }; +#pragma pack(pop) + + static int getSize(int entries) { return (entries == 0) ? 0 : sizeof(quint16) + sizeof(Entry) * entries; } + + StackArray() : QByteArray() { } + StackArray(int entries) : QByteArray(getSize(entries), 0) { } + StackArray(const QByteArray& other) : QByteArray(other) { } + StackArray(const char* src, int bytes) : QByteArray(src, bytes) { } + + int getPosition() const { return *(const quint16*)constData(); } + void setPosition(int position) { *(quint16*)data() = position; } + + quint16& getPositionRef() { return *(quint16*)data(); } + + int getEntryCount() const { return isEmpty() ? 0 : (size() - sizeof(quint16)) / sizeof(Entry); } + + Entry* getEntryData() { return (Entry*)(data() + sizeof(quint16)); } + const Entry* getEntryData() const { return (const Entry*)(constData() + sizeof(quint16)); } + + int getEntryAlpha(int y, float heightfieldHeight = 0.0f) const; + + Entry& getEntry(int y, float heightfieldHeight = 0.0f); + const Entry& getEntry(int y, float heightfieldHeight = 0.0f) const; + + void getExtents(int& minimumY, int& maximumY) const; + + bool hasSetEntries() const; + + void removeEntries(int position, int count) { remove(sizeof(quint16) + position * sizeof(Entry), count * sizeof(Entry)); } +}; + +/// A block of stack data associated with a heightfield. +class HeightfieldStack : public HeightfieldData { +public: + + HeightfieldStack(int width, const QVector& contents, const QVector& materials); + HeightfieldStack(Bitstream& in, int bytes); + HeightfieldStack(Bitstream& in, int bytes, const HeightfieldStackPointer& reference); + + QVector& getContents() { return _contents; } + QVector& getMaterials() { return _materials; } + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldStackPointer& reference); + +private: + + void read(Bitstream& in, int bytes); + + QVector _contents; + QVector _materials; +}; + +Q_DECLARE_METATYPE(HeightfieldStackPointer) + +Bitstream& operator<<(Bitstream& out, const HeightfieldStackPointer& value); +Bitstream& operator>>(Bitstream& in, HeightfieldStackPointer& value); + +template<> void Bitstream::writeRawDelta(const HeightfieldStackPointer& value, const HeightfieldStackPointer& reference); +template<> void Bitstream::readRawDelta(HeightfieldStackPointer& value, const HeightfieldStackPointer& reference); + typedef QExplicitlySharedDataPointer HeightfieldNodePointer; /// Holds the base state used in streaming heightfield data. @@ -499,14 +658,15 @@ public: HeightfieldNode(const HeightfieldHeightPointer& height = HeightfieldHeightPointer(), const HeightfieldColorPointer& color = HeightfieldColorPointer(), - const HeightfieldMaterialPointer& material = HeightfieldMaterialPointer()); + const HeightfieldMaterialPointer& material = HeightfieldMaterialPointer(), + const HeightfieldStackPointer& stack = HeightfieldStackPointer()); HeightfieldNode(const HeightfieldNode& other); ~HeightfieldNode(); void setContents(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, - const HeightfieldMaterialPointer& material); + const HeightfieldMaterialPointer& material, const HeightfieldStackPointer& stack); void setHeight(const HeightfieldHeightPointer& height) { _height = height; } const HeightfieldHeightPointer& getHeight() const { return _height; } @@ -517,6 +677,9 @@ public: void setMaterial(const HeightfieldMaterialPointer& material) { _material = material; } const HeightfieldMaterialPointer& getMaterial() const { return _material; } + void setStack(const HeightfieldStackPointer& stack) { _stack = stack; } + const HeightfieldStackPointer& getStack() const { return _stack; } + void setRenderer(AbstractHeightfieldNodeRenderer* renderer) { _renderer = renderer; } AbstractHeightfieldNodeRenderer* getRenderer() const { return _renderer; } @@ -525,21 +688,25 @@ public: void setChild(int index, const HeightfieldNodePointer& child) { _children[index] = child; } const HeightfieldNodePointer& getChild(int index) const { return _children[index]; } - float getHeight(const glm::vec3& location) const; + bool findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + void getRangeAfterHeightPaint(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& position, float radius, float height, float& minimum, float& maximum) const; - HeightfieldNode* paintMaterial(const glm::vec3& position, const glm::vec3& radius, const SharedObjectPointer& material, - const QColor& color); - - void getRangeAfterHeightPaint(const glm::vec3& position, const glm::vec3& radius, - float height, int& minimum, int& maximum) const; - - HeightfieldNode* paintHeight(const glm::vec3& position, const glm::vec3& radius, float height, + HeightfieldNode* paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& position, float radius, float height, bool set, bool erase, + float normalizeScale, float normalizeOffset); + + HeightfieldNode* fillHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& position, float radius); + + void getRangeAfterEdit(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const Box& editBounds, float& minimum, float& maximum) const; + + HeightfieldNode* setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize, float normalizeScale, float normalizeOffset); - - HeightfieldNode* clearAndFetchHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, - const Box& bounds, SharedObjectPointer& heightfield); void read(HeightfieldStreamState& state); void write(HeightfieldStreamState& state) const; @@ -563,9 +730,16 @@ private: QRgb getColorAt(const glm::vec3& location) const; int getMaterialAt(const glm::vec3& location) const; + void maybeRenormalize(const glm::vec3& scale, float normalizeScale, float normalizeOffset, int innerStackWidth, + QVector& heightContents, QVector& stackContents); + + bool findHeightfieldRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float boundsDistance, float& distance) const; + HeightfieldHeightPointer _height; HeightfieldColorPointer _color; HeightfieldMaterialPointer _material; + HeightfieldStackPointer _stack; HeightfieldNodePointer _children[CHILD_COUNT]; @@ -577,6 +751,9 @@ class AbstractHeightfieldNodeRenderer { public: virtual ~AbstractHeightfieldNodeRenderer(); + + virtual bool findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const; }; /// A heightfield represented as a spanner. @@ -588,8 +765,9 @@ class Heightfield : public Transformable { Q_PROPERTY(HeightfieldColorPointer color MEMBER _color WRITE setColor NOTIFY colorChanged STORED false) Q_PROPERTY(HeightfieldMaterialPointer material MEMBER _material WRITE setMaterial NOTIFY materialChanged STORED false DESIGNABLE false) - Q_PROPERTY(HeightfieldNodePointer root MEMBER _root WRITE setRoot NOTIFY rootChanged STORED false DESIGNABLE false) - + Q_PROPERTY(HeightfieldStackPointer stack MEMBER _stack WRITE setStack NOTIFY stackChanged STORED false + DESIGNABLE false) + public: Q_INVOKABLE Heightfield(); @@ -609,7 +787,10 @@ public: void setMaterial(const HeightfieldMaterialPointer& material); const HeightfieldMaterialPointer& getMaterial() const { return _material; } - void setRoot(const HeightfieldNodePointer& root); + void setStack(const HeightfieldStackPointer& stack); + const HeightfieldStackPointer& getStack() const { return _stack; } + + void setRoot(const HeightfieldNodePointer& root) { _root = root; } const HeightfieldNodePointer& getRoot() const { return _root; } MetavoxelLOD transformLOD(const MetavoxelLOD& lod) const; @@ -622,13 +803,13 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - virtual Spanner* paintMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material, - const QColor& color); - - virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height); + virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase); - virtual Spanner* clearAndFetchHeight(const Box& bounds, SharedObjectPointer& heightfield); + virtual Spanner* fillHeight(const glm::vec3& position, float radius); + virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, + const QColor& color, bool paint, bool voxelize); + virtual bool hasOwnColors() const; virtual bool hasOwnMaterials() const; virtual QRgb getColorAt(const glm::vec3& point); @@ -652,7 +833,7 @@ signals: void heightChanged(const HeightfieldHeightPointer& height); void colorChanged(const HeightfieldColorPointer& color); void materialChanged(const HeightfieldMaterialPointer& material); - void rootChanged(const HeightfieldNodePointer& root); + void stackChanged(const HeightfieldStackPointer& stack); protected: @@ -665,12 +846,15 @@ private slots: private: + Heightfield* prepareEdit(float minimumValue, float maximumValue, float& normalizeScale, float& normalizeOffset); + float _aspectY; float _aspectZ; HeightfieldHeightPointer _height; HeightfieldColorPointer _color; HeightfieldMaterialPointer _material; + HeightfieldStackPointer _stack; HeightfieldNodePointer _root; }; diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 0607d1d795..5b9811a152 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -78,7 +78,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 10; + return 11; default: return 0; } diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 5376883438..aaec8b3e4a 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -262,7 +262,7 @@ bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direc return false; // origin below plane } float divisor = glm::dot(normal, direction); - if (divisor > -EPSILON) { + if (divisor >= 0.0f) { return false; } float t = dividend / divisor; @@ -490,4 +490,4 @@ void PolygonClip::copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& len vertexArrayA[i] = vertexArrayB[i]; } } -} \ No newline at end of file +}