diff --git a/interface/resources/shaders/metavoxel_heightfield_base.frag b/interface/resources/shaders/metavoxel_heightfield_base.frag new file mode 100644 index 0000000000..9b64a59e6f --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_base.frag @@ -0,0 +1,20 @@ +#version 120 + +// +// metavoxel_heightfield_base.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +void main(void) { + // compute the base color based on OpenGL lighting model + gl_FragColor = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_base.vert b/interface/resources/shaders/metavoxel_heightfield_base.vert new file mode 100644 index 0000000000..3e4b081d6f --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_base.vert @@ -0,0 +1,33 @@ +#version 120 + +// +// metavoxel_heightfield_base.vert +// vertex shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the height texture +uniform sampler2D heightMap; + +// the distance between height points in texture space +uniform float heightScale; + +// the scale between height and color textures +uniform float colorScale; + +void main(void) { + // add the height to the position + float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; + gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + + // the zero height should be invisible + gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0)); + + // pass along the scaled/offset texture coordinates + gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * colorScale; +} diff --git a/interface/resources/shaders/metavoxel_heightfield_light.frag b/interface/resources/shaders/metavoxel_heightfield_light.frag new file mode 100644 index 0000000000..ce3f23e142 --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_light.frag @@ -0,0 +1,21 @@ +#version 120 + +// +// metavoxel_heightfield_light.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the base color based on OpenGL lighting model + gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalize(normal), gl_LightSource[0].position))); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_light.vert b/interface/resources/shaders/metavoxel_heightfield_light.vert new file mode 100644 index 0000000000..228d575b81 --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_light.vert @@ -0,0 +1,45 @@ +#version 120 + +// +// metavoxel_heighfield_light.vert +// vertex shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the height texture +uniform sampler2D heightMap; + +// the distance between height points in texture space +uniform float heightScale; + +// the interpolated position +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // transform and store the normal for interpolation + vec2 heightCoord = gl_MultiTexCoord0.st; + float deltaX = texture2D(heightMap, heightCoord - vec2(heightScale, 0.0)).r - + texture2D(heightMap, heightCoord + vec2(heightScale, 0.0)).r; + float deltaZ = texture2D(heightMap, heightCoord - vec2(0.0, heightScale)).r - + texture2D(heightMap, heightCoord + vec2(0.0, heightScale)).r; + normal = normalize(gl_ModelViewMatrix * vec4(deltaX, heightScale, deltaZ, 0.0)); + + // add the height to the position + float height = texture2D(heightMap, heightCoord).r; + position = gl_ModelViewMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + gl_Position = gl_ProjectionMatrix * position; + + // the zero height should be invisible + gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0)); + + // and the shadow texture coordinates + gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag new file mode 100644 index 0000000000..73382eb83c --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag @@ -0,0 +1,44 @@ +#version 120 + +// +// metavoxel_heightfield_light_cascaded_shadow_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the distances to the cascade sections +uniform vec3 shadowDistances; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the index of the cascade to use and the corresponding texture coordinates + int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0))); + vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position), + dot(gl_EyePlaneR[shadowIndex], position)); + + // compute the base color based on OpenGL lighting model + float diffuse = dot(normalize(normal), gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r); + gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag new file mode 100644 index 0000000000..4f2df8958b --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag @@ -0,0 +1,33 @@ +#version 120 + +// +// metavoxel_heightfield_light_shadow_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the base color based on OpenGL lighting model + float diffuse = dot(normalize(normal), gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); + gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.frag b/interface/resources/shaders/metavoxel_heightfield_splat.frag new file mode 100644 index 0000000000..6a3c058b80 --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_splat.frag @@ -0,0 +1,28 @@ +#version 120 + +// +// metavoxel_heightfield_splat.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +const int SPLAT_COUNT = 4; + +// the splat textures +uniform sampler2D diffuseMaps[SPLAT_COUNT]; + +// alpha values for the four splat textures +varying vec4 alphaValues; + +void main(void) { + // blend the splat textures + gl_FragColor = gl_Color * (texture2D(diffuseMaps[0], gl_TexCoord[0].st) * alphaValues.x + + texture2D(diffuseMaps[1], gl_TexCoord[0].st) * alphaValues.y + + texture2D(diffuseMaps[2], gl_TexCoord[0].st) * alphaValues.z + + texture2D(diffuseMaps[3], gl_TexCoord[0].st) * alphaValues.w); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.vert b/interface/resources/shaders/metavoxel_heightfield_splat.vert new file mode 100644 index 0000000000..950162073e --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_splat.vert @@ -0,0 +1,50 @@ +#version 120 + +// +// metavoxel_heighfield_splat.vert +// vertex shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the height texture +uniform sampler2D heightMap; + +// the texture that contains the texture indices +uniform sampler2D textureMap; + +// the distance between height points in texture space +uniform float heightScale; + +// the scale between height and texture textures +uniform float textureScale; + +// the lower bounds of the values corresponding to the splat textures +uniform vec4 textureValueMinima; + +// the upper bounds of the values corresponding to the splat textures +uniform vec4 textureValueMaxima; + +// alpha values for the four splat textures +varying vec4 alphaValues; + +void main(void) { + // add the height to the position + float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; + gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + + // the zero height should be invisible + gl_FrontColor = vec4(1.0, 1.0, 1.0, 1.0 - step(height, 0.0)); + + // pass along the scaled/offset texture coordinates + gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * textureScale; + + // compute the alpha values for each texture + float value = texture2D(textureMap, gl_TexCoord[0].st).r; + vec4 valueVector = vec4(value, value, value, value); + alphaValues = step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima); +} diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 433c8af23e..3864b0537f 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -118,7 +118,7 @@ void MetavoxelSystem::render() { viewFrustum->getNearBottomLeft(), viewFrustum->getNearBottomRight()); RenderVisitor renderVisitor(getLOD()); - guideToAugmented(renderVisitor); + guideToAugmented(renderVisitor, true); } class RayHeightfieldIntersectionVisitor : public RayIntersectionVisitor { @@ -449,22 +449,29 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r glDepthFunc(GL_LESS); } -void MetavoxelSystem::deleteTextures(int heightID, int colorID) { +void MetavoxelSystem::deleteTextures(int heightID, int colorID, int textureID) { glDeleteTextures(1, (GLuint*)&heightID); glDeleteTextures(1, (GLuint*)&colorID); + glDeleteTextures(1, (GLuint*)&textureID); } MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) { return new MetavoxelSystemClient(node, _updater); } -void MetavoxelSystem::guideToAugmented(MetavoxelVisitor& visitor) { +void MetavoxelSystem::guideToAugmented(MetavoxelVisitor& visitor, bool render) { foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { if (node->getType() == NodeType::MetavoxelServer) { QMutexLocker locker(&node->getMutex()); MetavoxelSystemClient* client = static_cast(node->getLinkedData()); if (client) { - client->getAugmentedData().guide(visitor); + MetavoxelData data = client->getAugmentedData(); + data.guide(visitor); + if (render) { + // save the rendered augmented data so that its cached texture references, etc., don't + // get collected when we replace it with more recent versions + client->setRenderedAugmentedData(data); + } } } } @@ -601,15 +608,19 @@ const int HeightfieldBuffer::SHARED_EDGE = 1; const int HeightfieldBuffer::HEIGHT_EXTENSION = 2 * HeightfieldBuffer::HEIGHT_BORDER + HeightfieldBuffer::SHARED_EDGE; HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, - const QByteArray& height, const QByteArray& color) : + const QByteArray& height, const QByteArray& color, const QByteArray& texture, + const QVector& textures) : _translation(translation), _scale(scale), _heightBounds(translation, translation + glm::vec3(scale, scale, scale)), _colorBounds(_heightBounds), _height(height), _color(color), + _texture(texture), + _textures(textures), _heightTextureID(0), _colorTextureID(0), + _textureTextureID(0), _heightSize(glm::sqrt(height.size())), _heightIncrement(scale / (_heightSize - HEIGHT_EXTENSION)), _colorSize(glm::sqrt(color.size() / HeightfieldData::COLOR_BYTES)), @@ -628,10 +639,11 @@ HeightfieldBuffer::~HeightfieldBuffer() { // the textures have to be deleted on the main thread (for its opengl context) if (QThread::currentThread() != Application::getInstance()->thread()) { QMetaObject::invokeMethod(Application::getInstance()->getMetavoxels(), "deleteTextures", - Q_ARG(int, _heightTextureID), Q_ARG(int, _colorTextureID)); + Q_ARG(int, _heightTextureID), Q_ARG(int, _colorTextureID), Q_ARG(int, _textureTextureID)); } else { glDeleteTextures(1, &_heightTextureID); glDeleteTextures(1, &_colorTextureID); + glDeleteTextures(1, &_textureTextureID); } } @@ -667,13 +679,17 @@ public: glm::vec3 vertex; }; +const int SPLAT_COUNT = 4; +const GLint SPLAT_TEXTURE_UNITS[] = { 3, 4, 5, 6 }; + void HeightfieldBuffer::render(bool cursor) { // initialize textures, etc. on first render if (_heightTextureID == 0) { glGenTextures(1, &_heightTextureID); glBindTexture(GL_TEXTURE_2D, _heightTextureID); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, _heightSize, _heightSize, 0, @@ -692,6 +708,27 @@ void HeightfieldBuffer::render(bool cursor) { int colorSize = glm::sqrt(_color.size() / HeightfieldData::COLOR_BYTES); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, colorSize, colorSize, 0, GL_RGB, GL_UNSIGNED_BYTE, _color.constData()); } + + if (!_texture.isEmpty()) { + glGenTextures(1, &_textureTextureID); + glBindTexture(GL_TEXTURE_2D, _textureTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + 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); + int textureSize = glm::sqrt(_texture.size()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, textureSize, textureSize, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, _texture.constData()); + + _networkTextures.resize(_textures.size()); + for (int i = 0; i < _textures.size(); i++) { + const SharedObjectPointer texture = _textures.at(i); + if (texture) { + _networkTextures[i] = Application::getInstance()->getTextureCache()->getTexture( + static_cast(texture.data())->getURL(), SPLAT_TEXTURE); + } + } + } } // create the buffer objects lazily int innerSize = _heightSize - 2 * HeightfieldBuffer::HEIGHT_BORDER; @@ -759,7 +796,99 @@ void HeightfieldBuffer::render(bool cursor) { glBindTexture(GL_TEXTURE_2D, _heightTextureID); - if (!cursor) { + if (cursor) { + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + + } else if (!_textures.isEmpty()) { + DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getBaseHeightScaleLocation(), 1.0f / _heightSize); + DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getBaseColorScaleLocation(), (float)_heightSize / innerSize); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, _colorTextureID); + + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + + glDepthFunc(GL_LEQUAL); + glDepthMask(false); + glEnable(GL_BLEND); + glDisable(GL_ALPHA_TEST); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, -1.0f); + + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatHeightScaleLocation(), 1.0f / _heightSize); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureScaleLocation(), (float)_heightSize / innerSize); + + glBindTexture(GL_TEXTURE_2D, _textureTextureID); + + const int TEXTURES_PER_SPLAT = 4; + for (int i = 0; i < _textures.size(); i += TEXTURES_PER_SPLAT) { + for (int j = 0; j < SPLAT_COUNT; j++) { + glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[j]); + int index = i + j; + if (index < _networkTextures.size()) { + const NetworkTexturePointer& texture = _networkTextures.at(index); + glBindTexture(GL_TEXTURE_2D, texture ? texture->getID() : 0); + } else { + glBindTexture(GL_TEXTURE_2D, 0); + } + } + const float QUARTER_STEP = 0.25f * EIGHT_BIT_MAXIMUM_RECIPROCAL; + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureValueMinimaLocation(), + (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, + (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureValueMaximaLocation(), + (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, + (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP); + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + } + + glEnable(GL_ALPHA_TEST); + glBlendFunc(GL_DST_COLOR, GL_ZERO); + + for (int i = 0; i < SPLAT_COUNT; i++) { + glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[i]); + glBindTexture(GL_TEXTURE_2D, 0); + } + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + + if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) { + DefaultMetavoxelRendererImplementation::getShadowLightHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getShadowLightHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getShadowLightHeightScaleLocation(), 1.0f / _heightSize); + + } else if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightScaleLocation(), 1.0f / _heightSize); + + } else { + DefaultMetavoxelRendererImplementation::getLightHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getLightHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getBaseHeightScaleLocation(), 1.0f / _heightSize); + } + + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + + DefaultMetavoxelRendererImplementation::getHeightfieldProgram().bind(); + + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + glDepthFunc(GL_LESS); + glDepthMask(true); + + glActiveTexture(GL_TEXTURE0); + + } else { int heightScaleLocation = DefaultMetavoxelRendererImplementation::getHeightScaleLocation(); int colorScaleLocation = DefaultMetavoxelRendererImplementation::getColorScaleLocation(); ProgramObject* program = &DefaultMetavoxelRendererImplementation::getHeightfieldProgram(); @@ -777,11 +906,9 @@ void HeightfieldBuffer::render(bool cursor) { program->setUniformValue(colorScaleLocation, (float)_heightSize / innerSize); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, _colorTextureID); - } - - glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); - - if (!cursor) { + + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); } @@ -898,6 +1025,71 @@ void DefaultMetavoxelRendererImplementation::init() { _shadowDistancesLocation = _cascadedShadowMapHeightfieldProgram.uniformLocation("shadowDistances"); _cascadedShadowMapHeightfieldProgram.release(); + _baseHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_base.vert"); + _baseHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_base.frag"); + _baseHeightfieldProgram.link(); + + _baseHeightfieldProgram.bind(); + _baseHeightfieldProgram.setUniformValue("heightMap", 0); + _baseHeightfieldProgram.setUniformValue("diffuseMap", 1); + _baseHeightScaleLocation = _heightfieldProgram.uniformLocation("heightScale"); + _baseColorScaleLocation = _heightfieldProgram.uniformLocation("colorScale"); + _baseHeightfieldProgram.release(); + + _splatHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_splat.vert"); + _splatHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_splat.frag"); + _splatHeightfieldProgram.link(); + + _splatHeightfieldProgram.bind(); + _splatHeightfieldProgram.setUniformValue("heightMap", 0); + _splatHeightfieldProgram.setUniformValue("textureMap", 1); + _splatHeightfieldProgram.setUniformValueArray("diffuseMaps", SPLAT_TEXTURE_UNITS, SPLAT_COUNT); + _splatHeightScaleLocation = _splatHeightfieldProgram.uniformLocation("heightScale"); + _splatTextureScaleLocation = _splatHeightfieldProgram.uniformLocation("textureScale"); + _splatTextureValueMinimaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMinima"); + _splatTextureValueMaximaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMaxima"); + _splatHeightfieldProgram.release(); + + _lightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light.vert"); + _lightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light.frag"); + _lightHeightfieldProgram.link(); + + _lightHeightfieldProgram.bind(); + _lightHeightfieldProgram.setUniformValue("heightMap", 0); + _lightHeightScaleLocation = _lightHeightfieldProgram.uniformLocation("heightScale"); + _lightHeightfieldProgram.release(); + + _shadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light.vert"); + _shadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light_shadow_map.frag"); + _shadowLightHeightfieldProgram.link(); + + _shadowLightHeightfieldProgram.bind(); + _shadowLightHeightfieldProgram.setUniformValue("heightMap", 0); + _shadowLightHeightfieldProgram.setUniformValue("shadowMap", 2); + _shadowLightHeightScaleLocation = _shadowLightHeightfieldProgram.uniformLocation("heightScale"); + _shadowLightHeightfieldProgram.release(); + + _cascadedShadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light.vert"); + _cascadedShadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag"); + _cascadedShadowLightHeightfieldProgram.link(); + + _cascadedShadowLightHeightfieldProgram.bind(); + _cascadedShadowLightHeightfieldProgram.setUniformValue("heightMap", 0); + _cascadedShadowLightHeightfieldProgram.setUniformValue("shadowMap", 2); + _cascadedShadowLightHeightScaleLocation = _cascadedShadowLightHeightfieldProgram.uniformLocation("heightScale"); + _shadowLightDistancesLocation = _cascadedShadowLightHeightfieldProgram.uniformLocation("shadowDistances"); + _cascadedShadowLightHeightfieldProgram.release(); + _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_heightfield_cursor.vert"); _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + @@ -1011,7 +1203,7 @@ int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { if (!info.isLeaf && info.size > _buffer->getScale()) { return DEFAULT_ORDER; } - HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); + HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue(); if (!height) { return STOP_RECURSION; } @@ -1065,11 +1257,11 @@ int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { int colorSize = _buffer->getColorSize(); if (colorSize == 0) { - return STOP_RECURSION; + continue; } - HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); + HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); if (!color) { - return STOP_RECURSION; + continue; } const Box& colorBounds = _buffer->getColorBounds(); overlap = colorBounds.getIntersection(overlap); @@ -1138,6 +1330,7 @@ private: HeightfieldRegionVisitor::HeightfieldRegionVisitor(const MetavoxelLOD& lod) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << + AttributeRegistry::getInstance()->getHeightfieldTextureAttribute() << Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector() << Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod), regionBounds(glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX), glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX)), @@ -1149,14 +1342,14 @@ int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) { return DEFAULT_ORDER; } HeightfieldBuffer* buffer = NULL; - HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); + HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue(); if (height) { const QByteArray& heightContents = height->getContents(); int size = glm::sqrt(heightContents.size()); int extendedSize = size + HeightfieldBuffer::HEIGHT_EXTENSION; int heightContentsSize = extendedSize * extendedSize; - HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); + HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); int colorContentsSize = 0; if (color) { const QByteArray& colorContents = color->getContents(); @@ -1165,33 +1358,44 @@ int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) { colorContentsSize = extendedColorSize * extendedColorSize * HeightfieldData::COLOR_BYTES; } + HeightfieldTextureDataPointer texture = info.inputValues.at(2).getInlineValue(); + QByteArray textureContents; + QVector textures; + if (texture) { + textureContents = texture->getContents(); + textures = texture->getTextures(); + } + const HeightfieldBuffer* existingBuffer = static_cast( - info.inputValues.at(2).getInlineValue().data()); + info.inputValues.at(3).getInlineValue().data()); Box bounds = info.getBounds(); if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize && existingBuffer->getColor().size() == colorContentsSize) { // we already have a buffer of the correct resolution addRegion(bounds, existingBuffer->getHeightBounds()); - return STOP_RECURSION; + buffer = new HeightfieldBuffer(info.minimum, info.size, existingBuffer->getHeight(), + existingBuffer->getColor(), textureContents, textures); + + } else { + // we must create a new buffer and update its borders + buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), + QByteArray(colorContentsSize, 0), textureContents, textures); + const Box& heightBounds = buffer->getHeightBounds(); + addRegion(bounds, heightBounds); + + _intersections.clear(); + _intersections.append(Box(heightBounds.minimum, + glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z))); + _intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z), + glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z))); + _intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z), + heightBounds.maximum)); + _intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z), + glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z))); + + _fetchVisitor.init(buffer); + _data->guide(_fetchVisitor); } - // we must create a new buffer and update its borders - buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), - QByteArray(colorContentsSize, 0)); - const Box& heightBounds = buffer->getHeightBounds(); - addRegion(bounds, heightBounds); - - _intersections.clear(); - _intersections.append(Box(heightBounds.minimum, - glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z))); - _intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z), - glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z))); - _intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z), - heightBounds.maximum)); - _intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z), - glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z))); - - _fetchVisitor.init(buffer); - _data->guide(_fetchVisitor); } info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer))); return STOP_RECURSION; @@ -1249,7 +1453,7 @@ int HeightfieldUpdateVisitor::visit(MetavoxelInfo& info) { return STOP_RECURSION; } HeightfieldBuffer* newBuffer = new HeightfieldBuffer(info.minimum, info.size, - buffer->getHeight(), buffer->getColor()); + buffer->getHeight(), buffer->getColor(), buffer->getTexture(), buffer->getTextures()); _fetchVisitor.init(newBuffer); _data->guide(_fetchVisitor); info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(newBuffer))); @@ -1437,6 +1641,9 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox ProgramObject* program = &_heightfieldProgram; if (Menu::getInstance()->getShadowsEnabled()) { if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + _cascadedShadowLightHeightfieldProgram.bind(); + _cascadedShadowLightHeightfieldProgram.setUniform(_shadowLightDistancesLocation, + Application::getInstance()->getShadowDistances()); program = &_cascadedShadowMapHeightfieldProgram; program->bind(); program->setUniform(_shadowDistancesLocation, Application::getInstance()->getShadowDistances()); @@ -1482,6 +1689,21 @@ ProgramObject DefaultMetavoxelRendererImplementation::_cascadedShadowMapHeightfi int DefaultMetavoxelRendererImplementation::_cascadedShadowMapHeightScaleLocation; int DefaultMetavoxelRendererImplementation::_cascadedShadowMapColorScaleLocation; int DefaultMetavoxelRendererImplementation::_shadowDistancesLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_baseHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_baseHeightScaleLocation; +int DefaultMetavoxelRendererImplementation::_baseColorScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_splatHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_splatHeightScaleLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureScaleLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureValueMinimaLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureValueMaximaLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_lightHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_lightHeightScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_shadowLightHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_shadowLightHeightScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_cascadedShadowLightHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_cascadedShadowLightHeightScaleLocation; +int DefaultMetavoxelRendererImplementation::_shadowLightDistancesLocation; ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldCursorProgram; static void enableClipPlane(GLenum plane, float x, float y, float z, float w) { diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 38d67bcaed..ac4dd430fb 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -49,7 +49,7 @@ public: Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location); - Q_INVOKABLE void deleteTextures(int heightID, int colorID); + Q_INVOKABLE void deleteTextures(int heightID, int colorID, int textureID); protected: @@ -57,7 +57,7 @@ protected: private: - void guideToAugmented(MetavoxelVisitor& visitor); + void guideToAugmented(MetavoxelVisitor& visitor, bool render = false); AttributePointer _pointBufferAttribute; AttributePointer _heightfieldBufferAttribute; @@ -92,6 +92,8 @@ public: /// Returns a copy of the augmented data. This function is thread-safe. MetavoxelData getAugmentedData(); + void setRenderedAugmentedData(const MetavoxelData& data) { _renderedAugmentedData = data; } + virtual int parseData(const QByteArray& packet); protected: @@ -102,6 +104,7 @@ protected: private: MetavoxelData _augmentedData; + MetavoxelData _renderedAugmentedData; QReadWriteLock _augmentedDataLock; }; @@ -139,7 +142,9 @@ public: static const int SHARED_EDGE; static const int HEIGHT_EXTENSION; - HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color); + HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, + const QByteArray& color, const QByteArray& texture = QByteArray(), + const QVector& textures = QVector()); ~HeightfieldBuffer(); const glm::vec3& getTranslation() const { return _translation; } @@ -154,6 +159,11 @@ public: QByteArray& getColor() { return _color; } const QByteArray& getColor() const { return _color; } + QByteArray& getTexture() { return _texture; } + const QByteArray& getTexture() const { return _texture; } + + const QVector& getTextures() const { return _textures; } + QByteArray getUnextendedHeight() const; QByteArray getUnextendedColor() const; @@ -173,13 +183,17 @@ private: Box _colorBounds; QByteArray _height; QByteArray _color; + QByteArray _texture; + QVector _textures; GLuint _heightTextureID; GLuint _colorTextureID; + GLuint _textureTextureID; + QVector _networkTextures; int _heightSize; float _heightIncrement; int _colorSize; float _colorIncrement; - + typedef QPair BufferPair; static QHash _bufferPairs; }; @@ -231,6 +245,25 @@ public: static int getCascadedShadowMapHeightScaleLocation() { return _cascadedShadowMapHeightScaleLocation; } static int getCascadedShadowMapColorScaleLocation() { return _cascadedShadowMapColorScaleLocation; } + static ProgramObject& getBaseHeightfieldProgram() { return _baseHeightfieldProgram; } + static int getBaseHeightScaleLocation() { return _baseHeightScaleLocation; } + static int getBaseColorScaleLocation() { return _baseColorScaleLocation; } + + static ProgramObject& getSplatHeightfieldProgram() { return _splatHeightfieldProgram; } + static int getSplatHeightScaleLocation() { return _splatHeightScaleLocation; } + static int getSplatTextureScaleLocation() { return _splatTextureScaleLocation; } + static int getSplatTextureValueMinimaLocation() { return _splatTextureValueMinimaLocation; } + static int getSplatTextureValueMaximaLocation() { return _splatTextureValueMaximaLocation; } + + static ProgramObject& getLightHeightfieldProgram() { return _lightHeightfieldProgram; } + static int getLightHeightScaleLocation() { return _lightHeightScaleLocation; } + + static ProgramObject& getShadowLightHeightfieldProgram() { return _shadowLightHeightfieldProgram; } + static int getShadowLightHeightScaleLocation() { return _shadowLightHeightScaleLocation; } + + static ProgramObject& getCascadedShadowLightHeightfieldProgram() { return _cascadedShadowLightHeightfieldProgram; } + static int getCascadedShadowLightHeightScaleLocation() { return _cascadedShadowLightHeightScaleLocation; } + static ProgramObject& getHeightfieldCursorProgram() { return _heightfieldCursorProgram; } Q_INVOKABLE DefaultMetavoxelRendererImplementation(); @@ -257,6 +290,26 @@ private: static int _cascadedShadowMapColorScaleLocation; static int _shadowDistancesLocation; + static ProgramObject _baseHeightfieldProgram; + static int _baseHeightScaleLocation; + static int _baseColorScaleLocation; + + static ProgramObject _splatHeightfieldProgram; + static int _splatHeightScaleLocation; + static int _splatTextureScaleLocation; + static int _splatTextureValueMinimaLocation; + static int _splatTextureValueMaximaLocation; + + static ProgramObject _lightHeightfieldProgram; + static int _lightHeightScaleLocation; + + static ProgramObject _shadowLightHeightfieldProgram; + static int _shadowLightHeightScaleLocation; + + static ProgramObject _cascadedShadowLightHeightfieldProgram; + static int _cascadedShadowLightHeightScaleLocation; + static int _shadowLightDistancesLocation; + static ProgramObject _heightfieldCursorProgram; }; diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index b5bd63ab87..3cfc5efd5f 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -593,17 +593,20 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { NetworkMeshPart networkPart; if (!part.diffuseTexture.filename.isEmpty()) { networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.diffuseTexture.filename)), false, mesh.isEye, part.diffuseTexture.content); + _textureBase.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE, + mesh.isEye, part.diffuseTexture.content); networkPart.diffuseTexture->setLoadPriorities(_loadPriorities); } if (!part.normalTexture.filename.isEmpty()) { networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content); + _textureBase.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE, + false, part.normalTexture.content); networkPart.normalTexture->setLoadPriorities(_loadPriorities); } if (!part.specularTexture.filename.isEmpty()) { networkPart.specularTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.specularTexture.filename)), true, false, part.specularTexture.content); + _textureBase.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE, + false, part.specularTexture.content); networkPart.specularTexture->setLoadPriorities(_loadPriorities); } networkMesh.parts.append(networkPart); diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 01c3dc1cc1..c3e58d52bb 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -145,6 +145,8 @@ GLuint TextureCache::getPermutationNormalTextureID() { } const unsigned char OPAQUE_WHITE[] = { 0xFF, 0xFF, 0xFF, 0xFF }; +const unsigned char TRANSPARENT_WHITE[] = { 0xFF, 0xFF, 0xFF, 0x0 }; +const unsigned char OPAQUE_BLACK[] = { 0x0, 0x0, 0x0, 0xFF }; const unsigned char OPAQUE_BLUE[] = { 0x80, 0x80, 0xFF, 0xFF }; static void loadSingleColorTexture(const unsigned char* color) { @@ -175,19 +177,18 @@ GLuint TextureCache::getBlueTextureID() { /// Extra data for creating textures. class TextureExtra { public: - bool normalMap; + TextureType type; const QByteArray& content; }; -QSharedPointer TextureCache::getTexture(const QUrl& url, bool normalMap, - bool dilatable, const QByteArray& content) { +NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type, bool dilatable, const QByteArray& content) { if (!dilatable) { - TextureExtra extra = { normalMap, content }; + TextureExtra extra = { type, content }; return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast(); } - QSharedPointer texture = _dilatableNetworkTextures.value(url); + NetworkTexturePointer texture = _dilatableNetworkTextures.value(url); if (texture.isNull()) { - texture = QSharedPointer(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared); + texture = NetworkTexturePointer(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared); texture->setSelf(texture); texture->setCache(this); _dilatableNetworkTextures.insert(url, texture); @@ -293,7 +294,7 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) { QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { const TextureExtra* textureExtra = static_cast(extra); - return QSharedPointer(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content), + return QSharedPointer(new NetworkTexture(url, textureExtra->type, textureExtra->content), &Resource::allReferencesCleared); } @@ -317,7 +318,7 @@ Texture::~Texture() { glDeleteTextures(1, &_id); } -NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content) : +NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) : Resource(url, !content.isEmpty()), _translucent(false) { @@ -325,9 +326,25 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray _loaded = true; } - // default to white/blue + // default to white/blue/black glBindTexture(GL_TEXTURE_2D, getID()); - loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE); + switch (type) { + case NORMAL_TEXTURE: + loadSingleColorTexture(OPAQUE_BLUE); + break; + + case SPECULAR_TEXTURE: + loadSingleColorTexture(OPAQUE_BLACK); + break; + + case SPLAT_TEXTURE: + loadSingleColorTexture(TRANSPARENT_WHITE); + break; + + default: + loadSingleColorTexture(OPAQUE_WHITE); + break; + } glBindTexture(GL_TEXTURE_2D, 0); // if we have content, load it after we have our self pointer @@ -382,12 +399,24 @@ void ImageReader::run() { qDebug() << "Image greater than maximum size:" << _url << image.width() << image.height(); image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio); } + int imageArea = image.width() * image.height(); + const int EIGHT_BIT_MAXIMUM = 255; if (!image.hasAlphaChannel()) { if (image.format() != QImage::Format_RGB888) { image = image.convertToFormat(QImage::Format_RGB888); } - QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false)); + int redTotal = 0, greenTotal = 0, blueTotal = 0; + for (int y = 0; y < image.height(); y++) { + for (int x = 0; x < image.width(); x++) { + QRgb rgb = image.pixel(x, y); + redTotal += qRed(rgb); + greenTotal += qGreen(rgb); + blueTotal += qBlue(rgb); + } + } + QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false), + Q_ARG(const QColor&, QColor(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea))); return; } if (image.format() != QImage::Format_ARGB32) { @@ -397,11 +426,15 @@ void ImageReader::run() { // check for translucency/false transparency int opaquePixels = 0; int translucentPixels = 0; - const int EIGHT_BIT_MAXIMUM = 255; - const int RGB_BITS = 24; + int redTotal = 0, greenTotal = 0, blueTotal = 0, alphaTotal = 0; for (int y = 0; y < image.height(); y++) { for (int x = 0; x < image.width(); x++) { - int alpha = image.pixel(x, y) >> RGB_BITS; + QRgb rgb = image.pixel(x, y); + redTotal += qRed(rgb); + greenTotal += qGreen(rgb); + blueTotal += qBlue(rgb); + int alpha = qAlpha(rgb); + alphaTotal += alpha; if (alpha == EIGHT_BIT_MAXIMUM) { opaquePixels++; } else if (alpha != 0) { @@ -409,13 +442,13 @@ void ImageReader::run() { } } } - int imageArea = image.width() * image.height(); if (opaquePixels == imageArea) { qDebug() << "Image with alpha channel is completely opaque:" << _url; image = image.convertToFormat(QImage::Format_RGB888); } QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), - Q_ARG(bool, translucentPixels >= imageArea / 2)); + Q_ARG(bool, translucentPixels >= imageArea / 2), Q_ARG(const QColor&, QColor(redTotal / imageArea, + greenTotal / imageArea, blueTotal / imageArea, alphaTotal / imageArea))); } void NetworkTexture::downloadFinished(QNetworkReply* reply) { @@ -427,8 +460,9 @@ void NetworkTexture::loadContent(const QByteArray& content) { QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content)); } -void NetworkTexture::setImage(const QImage& image, bool translucent) { +void NetworkTexture::setImage(const QImage& image, bool translucent, const QColor& averageColor) { _translucent = translucent; + _averageColor = averageColor; finishedLoading(true); imageLoaded(image); @@ -449,7 +483,7 @@ void NetworkTexture::imageLoaded(const QImage& image) { } DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, const QByteArray& content) : - NetworkTexture(url, false, content), + NetworkTexture(url, DEFAULT_TEXTURE, content), _innerRadius(0), _outerRadius(0) { diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index 248a451e3a..83d9b6cc74 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -23,6 +23,10 @@ class QOpenGLFramebufferObject; class NetworkTexture; +typedef QSharedPointer NetworkTexturePointer; + +enum TextureType { DEFAULT_TEXTURE, NORMAL_TEXTURE, SPECULAR_TEXTURE, SPLAT_TEXTURE }; + /// Stores cached textures, including render-to-texture targets. class TextureCache : public ResourceCache { Q_OBJECT @@ -47,7 +51,7 @@ public: GLuint getBlueTextureID(); /// Loads a texture from the specified URL. - QSharedPointer getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false, + NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, bool dilatable = false, const QByteArray& content = QByteArray()); /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is @@ -121,24 +125,28 @@ class NetworkTexture : public Resource, public Texture { public: - NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content); + NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content); /// Checks whether it "looks like" this texture is translucent /// (majority of pixels neither fully opaque or fully transparent). bool isTranslucent() const { return _translucent; } + /// Returns the lazily-computed average texture color. + const QColor& getAverageColor() const { return _averageColor; } + protected: virtual void downloadFinished(QNetworkReply* reply); Q_INVOKABLE void loadContent(const QByteArray& content); - Q_INVOKABLE void setImage(const QImage& image, bool translucent); + Q_INVOKABLE void setImage(const QImage& image, bool translucent, const QColor& averageColor); virtual void imageLoaded(const QImage& image); private: bool _translucent; + QColor _averageColor; }; /// Caches derived, dilated textures. diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index b7057532fb..ce09e3657d 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -120,6 +120,7 @@ MetavoxelEditor::MetavoxelEditor() : addTool(new EraseHeightfieldTool(this)); addTool(new HeightfieldHeightBrushTool(this)); addTool(new HeightfieldColorBrushTool(this)); + addTool(new HeightfieldTextureBrushTool(this)); updateAttributes(); @@ -956,14 +957,29 @@ void ImportHeightfieldTool::apply() { HeightfieldBuffer* buffer = static_cast(bufferData.data()); MetavoxelData data; data.setSize(scale); - HeightfieldDataPointer heightPointer(new HeightfieldData(buffer->getUnextendedHeight())); + + QByteArray height = buffer->getUnextendedHeight(); + HeightfieldHeightDataPointer heightPointer(new HeightfieldHeightData(height)); data.setRoot(AttributeRegistry::getInstance()->getHeightfieldAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldAttribute(), encodeInline(heightPointer)))); - if (!buffer->getColor().isEmpty()) { - HeightfieldDataPointer colorPointer(new HeightfieldData(buffer->getUnextendedColor())); - data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue( - AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); + + QByteArray color; + if (buffer->getColor().isEmpty()) { + const int WHITE_VALUE = 0xFF; + color = QByteArray(height.size() * HeightfieldData::COLOR_BYTES, WHITE_VALUE); + } else { + color = buffer->getUnextendedColor(); } + HeightfieldColorDataPointer colorPointer(new HeightfieldColorData(color)); + data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue( + AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); + + int size = glm::sqrt(height.size()) + HeightfieldBuffer::SHARED_EDGE; + QByteArray texture(size * size, 0); + HeightfieldTextureDataPointer texturePointer(new HeightfieldTextureData(texture)); + data.setRoot(AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), new MetavoxelNode(AttributeValue( + AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), encodeInline(texturePointer)))); + MetavoxelEditMessage message = { QVariant::fromValue(SetDataEdit( _translation->getValue() + buffer->getTranslation() * scale, data)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); @@ -1100,6 +1116,10 @@ HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QStrin _radius->setValue(1.0); } +bool HeightfieldBrushTool::appliesTo(const AttributePointer& attribute) const { + return attribute->inherits("HeightfieldAttribute"); +} + void HeightfieldBrushTool::render() { if (Application::getInstance()->isMouseHidden()) { return; @@ -1153,5 +1173,29 @@ HeightfieldColorBrushTool::HeightfieldColorBrushTool(MetavoxelEditor* editor) : } QVariant HeightfieldColorBrushTool::createEdit(bool alternate) { - return QVariant::fromValue(PaintHeightfieldColorEdit(_position, _radius->value(), _color->getColor())); + return QVariant::fromValue(PaintHeightfieldColorEdit(_position, _radius->value(), + alternate ? QColor() : _color->getColor())); +} + +HeightfieldTextureBrushTool::HeightfieldTextureBrushTool(MetavoxelEditor* editor) : + HeightfieldBrushTool(editor, "Texture Brush") { + + _form->addRow(_textureEditor = new SharedObjectEditor(&HeightfieldTexture::staticMetaObject, false)); + connect(_textureEditor, &SharedObjectEditor::objectChanged, this, &HeightfieldTextureBrushTool::updateTexture); +} + +QVariant HeightfieldTextureBrushTool::createEdit(bool alternate) { + if (alternate) { + return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), SharedObjectPointer(), QColor())); + } else { + SharedObjectPointer texture = _textureEditor->getObject(); + _textureEditor->detachObject(); + return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), texture, + _texture ? _texture->getAverageColor() : QColor())); + } +} + +void HeightfieldTextureBrushTool::updateTexture() { + HeightfieldTexture* texture = static_cast(_textureEditor->getObject().data()); + _texture = Application::getInstance()->getTextureCache()->getTexture(texture->getURL()); } diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 87d95a6927..7e37b819d7 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -28,6 +28,7 @@ class QScrollArea; class QSpinBox; class MetavoxelTool; +class SharedObjectEditor; class Vec3Editor; /// Allows editing metavoxels. @@ -311,6 +312,8 @@ public: HeightfieldBrushTool(MetavoxelEditor* editor, const QString& name); + virtual bool appliesTo(const AttributePointer& attribute) const; + virtual void render(); virtual bool eventFilter(QObject* watched, QEvent* event); @@ -359,4 +362,26 @@ private: QColorEditor* _color; }; +/// Allows texturing parts of the heightfield. +class HeightfieldTextureBrushTool : public HeightfieldBrushTool { + Q_OBJECT + +public: + + HeightfieldTextureBrushTool(MetavoxelEditor* editor); + +protected: + + virtual QVariant createEdit(bool alternate); + +private slots: + + void updateTexture(); + +private: + + SharedObjectEditor* _textureEditor; + QSharedPointer _texture; +}; + #endif // hifi_MetavoxelEditor_h diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 1e30aee576..e823e081cc 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -23,8 +23,10 @@ REGISTER_META_OBJECT(QRgbAttribute) REGISTER_META_OBJECT(PackedNormalAttribute) REGISTER_META_OBJECT(SpannerQRgbAttribute) REGISTER_META_OBJECT(SpannerPackedNormalAttribute) +REGISTER_META_OBJECT(HeightfieldTexture) REGISTER_META_OBJECT(HeightfieldAttribute) REGISTER_META_OBJECT(HeightfieldColorAttribute) +REGISTER_META_OBJECT(HeightfieldTextureAttribute) REGISTER_META_OBJECT(SharedObjectAttribute) REGISTER_META_OBJECT(SharedObjectSetAttribute) REGISTER_META_OBJECT(SpannerSetAttribute) @@ -49,7 +51,8 @@ AttributeRegistry::AttributeRegistry() : _spannerNormalAttribute(registerAttribute(new SpannerPackedNormalAttribute("spannerNormal"))), _spannerMaskAttribute(registerAttribute(new FloatAttribute("spannerMask"))), _heightfieldAttribute(registerAttribute(new HeightfieldAttribute("heightfield"))), - _heightfieldColorAttribute(registerAttribute(new HeightfieldColorAttribute("heightfieldColor"))) { + _heightfieldColorAttribute(registerAttribute(new HeightfieldColorAttribute("heightfieldColor"))), + _heightfieldTextureAttribute(registerAttribute(new HeightfieldTextureAttribute("heightfieldTexture"))) { // our baseline LOD threshold is for voxels; spanners and heightfields are a different story const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f; @@ -58,6 +61,7 @@ AttributeRegistry::AttributeRegistry() : const float HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER = 32.0f; _heightfieldAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); _heightfieldColorAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); + _heightfieldTextureAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); } static QScriptValue qDebugFunction(QScriptContext* context, QScriptEngine* engine) { @@ -204,6 +208,16 @@ Attribute::Attribute(const QString& name) : Attribute::~Attribute() { } +void Attribute::readSubdivided(MetavoxelStreamState& state, void*& value, + const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const { + read(state.base.stream, value, isLeaf); +} + +void Attribute::writeSubdivided(MetavoxelStreamState& state, void* value, + const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const { + write(state.base.stream, value, isLeaf); +} + MetavoxelNode* Attribute::createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const { return new MetavoxelNode(value); } @@ -260,9 +274,7 @@ MetavoxelNode* Attribute::expandMetavoxelRoot(const MetavoxelNode& root) { MetavoxelNode* newGrandchild = new MetavoxelNode(attribute); newChild->setChild((index + j) % MetavoxelNode::CHILD_COUNT, newGrandchild); } - newChild->mergeChildren(attribute); } - newParent->mergeChildren(attribute); return newParent; } @@ -489,20 +501,19 @@ HeightfieldData::HeightfieldData(const QByteArray& contents) : _contents(contents) { } -HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) { - read(in, bytes, color); +HeightfieldData::~HeightfieldData() { } enum HeightfieldImage { NULL_HEIGHTFIELD_IMAGE, NORMAL_HEIGHTFIELD_IMAGE, DEFLATED_HEIGHTFIELD_IMAGE }; -static QByteArray encodeHeightfieldImage(const QImage& image) { +static QByteArray encodeHeightfieldImage(const QImage& image, bool lossless = false) { if (image.isNull()) { return QByteArray(1, NULL_HEIGHTFIELD_IMAGE); } QBuffer buffer; buffer.open(QIODevice::WriteOnly); const int JPEG_ENCODE_THRESHOLD = 16; - if (image.width() >= JPEG_ENCODE_THRESHOLD && image.height() >= JPEG_ENCODE_THRESHOLD) { + if (image.width() >= JPEG_ENCODE_THRESHOLD && image.height() >= JPEG_ENCODE_THRESHOLD && !lossless) { qint32 offsetX = image.offset().x(), offsetY = image.offset().y(); buffer.write((char*)&offsetX, sizeof(qint32)); buffer.write((char*)&offsetY, sizeof(qint32)); @@ -536,65 +547,93 @@ const QImage decodeHeightfieldImage(const QByteArray& data) { } } -HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color) { +HeightfieldHeightData::HeightfieldHeightData(const QByteArray& contents) : + HeightfieldData(contents) { +} + +HeightfieldHeightData::HeightfieldHeightData(Bitstream& in, int bytes) { + read(in, bytes); +} + +HeightfieldHeightData::HeightfieldHeightData(Bitstream& in, int bytes, const HeightfieldHeightDataPointer& reference) { if (!reference) { - read(in, bytes, color); + read(in, bytes); return; } - QMutexLocker locker(&reference->_encodedDeltaMutex); - reference->_encodedDelta = in.readAligned(bytes); - reference->_deltaData = this; - _contents = reference->_contents; - QImage image = decodeHeightfieldImage(reference->_encodedDelta); + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + reference->setEncodedDelta(in.readAligned(bytes)); + reference->setDeltaData(HeightfieldDataPointer(this)); + _contents = reference->getContents(); + QImage image = decodeHeightfieldImage(reference->getEncodedDelta()); if (image.isNull()) { return; } QPoint offset = image.offset(); image = image.convertToFormat(QImage::Format_RGB888); if (offset.x() == 0) { - set(image, color); + set(image); return; } int minX = offset.x() - 1; int minY = offset.y() - 1; - if (color) { - int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); - char* dest = _contents.data() + (minY * size + minX) * COLOR_BYTES; - int destStride = size * COLOR_BYTES; - int srcStride = image.width() * COLOR_BYTES; - for (int y = 0; y < image.height(); y++) { - memcpy(dest, image.constScanLine(y), srcStride); - dest += destStride; + int size = glm::sqrt((float)_contents.size()); + char* lineDest = _contents.data() + minY * size + minX; + for (int y = 0; y < image.height(); y++) { + const uchar* src = image.constScanLine(y); + for (char* dest = lineDest, *end = dest + image.width(); dest != end; dest++, src += COLOR_BYTES) { + *dest = *src; } - } else { - int size = glm::sqrt((float)_contents.size()); - char* lineDest = _contents.data() + minY * size + minX; - for (int y = 0; y < image.height(); y++) { - const uchar* src = image.constScanLine(y); - for (char* dest = lineDest, *end = dest + image.width(); dest != end; dest++, src += COLOR_BYTES) { - *dest = *src; - } - lineDest += size; + lineDest += size; + } +} + +HeightfieldHeightData::HeightfieldHeightData(Bitstream& in, int bytes, const HeightfieldHeightDataPointer& ancestor, + const glm::vec3& minimum, float size) { + QMutexLocker locker(&_encodedSubdivisionsMutex); + int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; + if (_encodedSubdivisions.size() <= index) { + _encodedSubdivisions.resize(index + 1); + } + EncodedSubdivision& subdivision = _encodedSubdivisions[index]; + subdivision.data = in.readAligned(bytes); + subdivision.ancestor = ancestor; + QImage image = decodeHeightfieldImage(subdivision.data); + if (image.isNull()) { + return; + } + image = image.convertToFormat(QImage::Format_RGB888); + int destSize = image.width(); + const uchar* src = image.constBits(); + const QByteArray& ancestorContents = ancestor->getContents(); + + int ancestorSize = glm::sqrt((float)ancestorContents.size()); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + + _contents = QByteArray(destSize * destSize, 0); + char* dest = _contents.data(); + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorSize; + float ancestorX = minimum.x * ancestorSize; + for (char* end = dest + destSize; dest != end; src += COLOR_BYTES, ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX; + *dest++ = *ref++ + *src; } } } -void HeightfieldData::write(Bitstream& out, bool color) { +void HeightfieldHeightData::write(Bitstream& out) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { QImage image; - if (color) { - int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); - image = QImage((uchar*)_contents.data(), size, size, QImage::Format_RGB888); - } else { - int size = glm::sqrt((float)_contents.size()); - image = QImage(size, size, QImage::Format_RGB888); - uchar* dest = image.bits(); - for (const char* src = _contents.constData(), *end = src + _contents.size(); src != end; src++) { - *dest++ = *src; - *dest++ = *src; - *dest++ = *src; - } + int size = glm::sqrt((float)_contents.size()); + image = QImage(size, size, QImage::Format_RGB888); + uchar* dest = image.bits(); + for (const char* src = _contents.constData(), *end = src + _contents.size(); src != end; src++) { + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; } _encoded = encodeHeightfieldImage(image); } @@ -602,114 +641,425 @@ void HeightfieldData::write(Bitstream& out, bool color) { out.writeAligned(_encoded); } -void HeightfieldData::writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color) { +void HeightfieldHeightData::writeDelta(Bitstream& out, const HeightfieldHeightDataPointer& reference) { if (!reference || reference->getContents().size() != _contents.size()) { - write(out, color); + write(out); return; } - QMutexLocker locker(&reference->_encodedDeltaMutex); - if (reference->_encodedDelta.isEmpty() || reference->_deltaData != this) { + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { QImage image; - int minX, minY; - if (color) { - int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); - minX = size; - minY = size; - int maxX = -1, maxY = -1; - const char* src = _contents.constData(); - const char* ref = reference->_contents.constData(); - for (int y = 0; y < size; y++) { - bool difference = false; - for (int x = 0; x < size; x++, src += COLOR_BYTES, ref += COLOR_BYTES) { - if (src[0] != ref[0] || src[1] != ref[1] || src[2] != ref[2]) { - minX = qMin(minX, x); - maxX = qMax(maxX, x); - difference = true; - } - } - if (difference) { - minY = qMin(minY, y); - maxY = qMax(maxY, y); + int size = glm::sqrt((float)_contents.size()); + int minX = size, minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->getContents().constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++) { + if (*src++ != *ref++) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; } } - if (maxX >= minX) { - int width = maxX - minX + 1; - int height = maxY - minY + 1; - image = QImage(width, height, QImage::Format_RGB888); - src = _contents.constData() + (minY * size + minX) * COLOR_BYTES; - int srcStride = size * COLOR_BYTES; - int destStride = width * COLOR_BYTES; - for (int y = 0; y < height; y++) { - memcpy(image.scanLine(y), src, destStride); - src += srcStride; - } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); } - } else { - int size = glm::sqrt((float)_contents.size()); - minX = size; - minY = size; - int maxX = -1, maxY = -1; - const char* src = _contents.constData(); - const char* ref = reference->_contents.constData(); - for (int y = 0; y < size; y++) { - bool difference = false; - for (int x = 0; x < size; x++) { - if (*src++ != *ref++) { - minX = qMin(minX, x); - maxX = qMax(maxX, x); - difference = true; - } - } - if (difference) { - minY = qMin(minY, y); - maxY = qMax(maxY, y); - } - } - if (maxX >= minX) { - int width = qMax(maxX - minX + 1, 0); - int height = qMax(maxY - minY + 1, 0); - image = QImage(width, height, QImage::Format_RGB888); - const uchar* lineSrc = (const uchar*)_contents.constData() + minY * size + minX; - for (int y = 0; y < height; y++) { - uchar* dest = image.scanLine(y); - for (const uchar* src = lineSrc, *end = src + width; src != end; src++) { - *dest++ = *src; - *dest++ = *src; - *dest++ = *src; - } - lineSrc += size; + } + if (maxX >= minX) { + int width = qMax(maxX - minX + 1, 0); + int height = qMax(maxY - minY + 1, 0); + image = QImage(width, height, QImage::Format_RGB888); + const uchar* lineSrc = (const uchar*)_contents.constData() + minY * size + minX; + for (int y = 0; y < height; y++) { + uchar* dest = image.scanLine(y); + for (const uchar* src = lineSrc, *end = src + width; src != end; src++) { + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; } + lineSrc += size; } } image.setOffset(QPoint(minX + 1, minY + 1)); - reference->_encodedDelta = encodeHeightfieldImage(image); - reference->_deltaData = this; + reference->setEncodedDelta(encodeHeightfieldImage(image)); + reference->setDeltaData(HeightfieldDataPointer(this)); } - out << reference->_encodedDelta.size(); - out.writeAligned(reference->_encodedDelta); + out << reference->getEncodedDelta().size(); + out.writeAligned(reference->getEncodedDelta()); } -void HeightfieldData::read(Bitstream& in, int bytes, bool color) { - set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888), color); -} - -void HeightfieldData::set(const QImage& image, bool color) { - if (color) { - _contents.resize(image.width() * image.height() * COLOR_BYTES); - memcpy(_contents.data(), image.constBits(), _contents.size()); +void HeightfieldHeightData::writeSubdivided(Bitstream& out, const HeightfieldHeightDataPointer& ancestor, + const glm::vec3& minimum, float size) { + QMutexLocker locker(&_encodedSubdivisionsMutex); + int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; + if (_encodedSubdivisions.size() <= index) { + _encodedSubdivisions.resize(index + 1); + } + EncodedSubdivision& subdivision = _encodedSubdivisions[index]; + if (subdivision.data.isEmpty() || subdivision.ancestor != ancestor) { + QImage image; + const QByteArray& ancestorContents = ancestor->getContents(); + const uchar* src = (const uchar*)_contents.constData(); - } else { - _contents.resize(image.width() * image.height()); - char* dest = _contents.data(); - for (const uchar* src = image.constBits(), *end = src + _contents.size() * COLOR_BYTES; - src != end; src += COLOR_BYTES) { - *dest++ = *src; + int destSize = glm::sqrt((float)_contents.size()); + image = QImage(destSize, destSize, QImage::Format_RGB888); + uchar* dest = image.bits(); + + int ancestorSize = glm::sqrt((float)ancestorContents.size()); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorSize; + float ancestorX = minimum.x * ancestorSize; + for (const uchar* end = src + destSize; src != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX; + uchar difference = *src++ - *ref; + *dest++ = difference; + *dest++ = difference; + *dest++ = difference; + } + } + subdivision.data = encodeHeightfieldImage(image, true); + subdivision.ancestor = ancestor; + } + out << subdivision.data.size(); + out.writeAligned(subdivision.data); +} + +void HeightfieldHeightData::read(Bitstream& in, int bytes) { + set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888)); +} + +void HeightfieldHeightData::set(const QImage& image) { + _contents.resize(image.width() * image.height()); + char* dest = _contents.data(); + for (const uchar* src = image.constBits(), *end = src + _contents.size() * COLOR_BYTES; + src != end; src += COLOR_BYTES) { + *dest++ = *src; + } +} + +HeightfieldColorData::HeightfieldColorData(const QByteArray& contents) : + HeightfieldData(contents) { +} + +HeightfieldColorData::HeightfieldColorData(Bitstream& in, int bytes) { + read(in, bytes); +} + +HeightfieldColorData::HeightfieldColorData(Bitstream& in, int bytes, const HeightfieldColorDataPointer& reference) { + if (!reference) { + read(in, bytes); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + reference->setEncodedDelta(in.readAligned(bytes)); + reference->setDeltaData(HeightfieldDataPointer(this)); + _contents = reference->getContents(); + QImage image = decodeHeightfieldImage(reference->getEncodedDelta()); + if (image.isNull()) { + return; + } + QPoint offset = image.offset(); + image = image.convertToFormat(QImage::Format_RGB888); + if (offset.x() == 0) { + set(image); + return; + } + int minX = offset.x() - 1; + int minY = offset.y() - 1; + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + char* dest = _contents.data() + (minY * size + minX) * COLOR_BYTES; + int destStride = size * COLOR_BYTES; + int srcStride = image.width() * COLOR_BYTES; + for (int y = 0; y < image.height(); y++) { + memcpy(dest, image.constScanLine(y), srcStride); + dest += destStride; + } +} + +HeightfieldColorData::HeightfieldColorData(Bitstream& in, int bytes, const HeightfieldColorDataPointer& ancestor, + const glm::vec3& minimum, float size) { + QMutexLocker locker(&_encodedSubdivisionsMutex); + int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; + if (_encodedSubdivisions.size() <= index) { + _encodedSubdivisions.resize(index + 1); + } + EncodedSubdivision& subdivision = _encodedSubdivisions[index]; + subdivision.data = in.readAligned(bytes); + subdivision.ancestor = ancestor; + QImage image = decodeHeightfieldImage(subdivision.data); + if (image.isNull()) { + return; + } + image = image.convertToFormat(QImage::Format_RGB888); + int destSize = image.width(); + const uchar* src = image.constBits(); + const QByteArray& ancestorContents = ancestor->getContents(); + + int ancestorSize = glm::sqrt(ancestorContents.size() / (float)COLOR_BYTES); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + int ancestorStride = ancestorSize * COLOR_BYTES; + + _contents = QByteArray(destSize * destSize * COLOR_BYTES, 0); + char* dest = _contents.data(); + int stride = image.width() * COLOR_BYTES; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorStride; + float ancestorX = minimum.x * ancestorSize; + for (char* end = dest + stride; dest != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX * COLOR_BYTES; + *dest++ = *ref++ + *src++; + *dest++ = *ref++ + *src++; + *dest++ = *ref++ + *src++; } } } +void HeightfieldColorData::write(Bitstream& out) { + QMutexLocker locker(&_encodedMutex); + if (_encoded.isEmpty()) { + QImage image; + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + image = QImage((uchar*)_contents.data(), size, size, QImage::Format_RGB888); + _encoded = encodeHeightfieldImage(image); + } + out << _encoded.size(); + out.writeAligned(_encoded); +} + +void HeightfieldColorData::writeDelta(Bitstream& out, const HeightfieldColorDataPointer& reference) { + if (!reference || reference->getContents().size() != _contents.size()) { + write(out); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { + QImage image; + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + int minX = size, minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->getContents().constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++, src += COLOR_BYTES, ref += COLOR_BYTES) { + if (src[0] != ref[0] || src[1] != ref[1] || src[2] != ref[2]) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + if (maxX >= minX) { + int width = maxX - minX + 1; + int height = maxY - minY + 1; + image = QImage(width, height, QImage::Format_RGB888); + src = _contents.constData() + (minY * size + minX) * COLOR_BYTES; + int srcStride = size * COLOR_BYTES; + int destStride = width * COLOR_BYTES; + for (int y = 0; y < height; y++) { + memcpy(image.scanLine(y), src, destStride); + src += srcStride; + } + } + image.setOffset(QPoint(minX + 1, minY + 1)); + reference->setEncodedDelta(encodeHeightfieldImage(image)); + reference->setDeltaData(HeightfieldDataPointer(this)); + } + out << reference->getEncodedDelta().size(); + out.writeAligned(reference->getEncodedDelta()); +} + +void HeightfieldColorData::writeSubdivided(Bitstream& out, const HeightfieldColorDataPointer& ancestor, + const glm::vec3& minimum, float size) { + QMutexLocker locker(&_encodedSubdivisionsMutex); + int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; + if (_encodedSubdivisions.size() <= index) { + _encodedSubdivisions.resize(index + 1); + } + EncodedSubdivision& subdivision = _encodedSubdivisions[index]; + if (subdivision.data.isEmpty() || subdivision.ancestor != ancestor) { + QImage image; + const QByteArray& ancestorContents = ancestor->getContents(); + const uchar* src = (const uchar*)_contents.constData(); + + int destSize = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + image = QImage(destSize, destSize, QImage::Format_RGB888); + uchar* dest = image.bits(); + int stride = destSize * COLOR_BYTES; + + int ancestorSize = glm::sqrt(ancestorContents.size() / (float)COLOR_BYTES); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + int ancestorStride = ancestorSize * COLOR_BYTES; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorStride; + float ancestorX = minimum.x * ancestorSize; + for (const uchar* end = src + stride; src != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX * COLOR_BYTES; + *dest++ = *src++ - *ref++; + *dest++ = *src++ - *ref++; + *dest++ = *src++ - *ref++; + } + } + subdivision.data = encodeHeightfieldImage(image, true); + subdivision.ancestor = ancestor; + } + out << subdivision.data.size(); + out.writeAligned(subdivision.data); +} + +void HeightfieldColorData::read(Bitstream& in, int bytes) { + set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888)); +} + +void HeightfieldColorData::set(const QImage& image) { + _contents.resize(image.width() * image.height() * COLOR_BYTES); + memcpy(_contents.data(), image.constBits(), _contents.size()); +} + +const int TEXTURE_HEADER_SIZE = sizeof(qint32) * 4; + +static QByteArray encodeTexture(int offsetX, int offsetY, int width, int height, const QByteArray& contents) { + QByteArray inflated(TEXTURE_HEADER_SIZE, 0); + qint32* header = (qint32*)inflated.data(); + *header++ = offsetX; + *header++ = offsetY; + *header++ = width; + *header++ = height; + inflated.append(contents); + return qCompress(inflated); +} + +static QByteArray decodeTexture(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++; + return inflated.mid(TEXTURE_HEADER_SIZE); +} + +HeightfieldTextureData::HeightfieldTextureData(const QByteArray& contents, const QVector& textures) : + HeightfieldData(contents), + _textures(textures) { +} + +HeightfieldTextureData::HeightfieldTextureData(Bitstream& in, int bytes) { + read(in, bytes); +} + +HeightfieldTextureData::HeightfieldTextureData(Bitstream& in, int bytes, const HeightfieldTextureDataPointer& reference) { + if (!reference) { + read(in, bytes); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + reference->setEncodedDelta(in.readAligned(bytes)); + in.readDelta(_textures, reference->getTextures()); + reference->setDeltaData(HeightfieldDataPointer(this)); + _contents = reference->getContents(); + + int offsetX, offsetY, width, height; + QByteArray delta = decodeTexture(reference->getEncodedDelta(), offsetX, offsetY, width, height); + if (delta.isEmpty()) { + return; + } + if (offsetX == 0) { + _contents = delta; + return; + } + int minX = offsetX - 1; + int minY = offsetY - 1; + int size = glm::sqrt((float)_contents.size()); + const char* src = delta.constData(); + char* dest = _contents.data() + minY * size + minX; + for (int y = 0; y < height; y++, src += width, dest += size) { + memcpy(dest, src, width); + } +} + +void HeightfieldTextureData::write(Bitstream& out) { + QMutexLocker locker(&_encodedMutex); + if (_encoded.isEmpty()) { + int size = glm::sqrt((float)_contents.size()); + _encoded = encodeTexture(0, 0, size, size, _contents); + } + out << _encoded.size(); + out.writeAligned(_encoded); + out << _textures; +} + +void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTextureDataPointer& reference) { + if (!reference || reference->getContents().size() != _contents.size()) { + write(out); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { + int size = glm::sqrt((float)_contents.size()); + int minX = size, minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->getContents().constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++) { + if (*src++ != *ref++) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + QByteArray delta; + int width = 0, height = 0; + if (maxX >= minX) { + width = maxX - minX + 1; + height = maxY - minY + 1; + delta = QByteArray(width * height, 0); + char* dest = delta.data(); + src = _contents.constData() + minY * size + minX; + for (int y = 0; y < height; y++, src += size, dest += width) { + memcpy(dest, src, width); + } + } + reference->setEncodedDelta(encodeTexture(minX + 1, minY + 1, width, height, delta)); + reference->setDeltaData(HeightfieldDataPointer(this)); + } + out << reference->getEncodedDelta().size(); + out.writeAligned(reference->getEncodedDelta()); + out.writeDelta(_textures, reference->getTextures()); +} + +void HeightfieldTextureData::read(Bitstream& in, int bytes) { + int offsetX, offsetY, width, height; + _contents = decodeTexture(_encoded = in.readAligned(bytes), offsetX, offsetY, width, height); + in >> _textures; +} + +HeightfieldTexture::HeightfieldTexture() { +} + HeightfieldAttribute::HeightfieldAttribute(const QString& name) : - InlineAttribute(name) { + InlineAttribute(name) { } void HeightfieldAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { @@ -719,9 +1069,9 @@ void HeightfieldAttribute::read(Bitstream& in, void*& value, bool isLeaf) const int size; in >> size; if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + *(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(); } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false)); + *(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(new HeightfieldHeightData(in, size)); } } @@ -729,9 +1079,9 @@ void HeightfieldAttribute::write(Bitstream& out, void* value, bool isLeaf) const if (!isLeaf) { return; } - HeightfieldDataPointer data = decodeInline(value); + HeightfieldHeightDataPointer data = decodeInline(value); if (data) { - data->write(out, false); + data->write(out); } else { out << 0; } @@ -744,10 +1094,10 @@ void HeightfieldAttribute::readDelta(Bitstream& in, void*& value, void* referenc int size; in >> size; if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + *(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(); } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData( - in, size, decodeInline(reference), false)); + *(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(new HeightfieldHeightData( + in, size, decodeInline(reference))); } } @@ -755,9 +1105,9 @@ void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* referen if (!isLeaf) { return; } - HeightfieldDataPointer data = decodeInline(value); + HeightfieldHeightDataPointer data = decodeInline(value); if (data) { - data->writeDelta(out, decodeInline(reference), false); + data->writeDelta(out, decodeInline(reference)); } else { out << 0; } @@ -766,20 +1116,20 @@ void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* referen bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead) const { int maxSize = 0; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer pointer = decodeInline(children[i]); + HeightfieldHeightDataPointer pointer = decodeInline(children[i]); if (pointer) { maxSize = qMax(maxSize, pointer->getContents().size()); } } if (maxSize == 0) { - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); + *(HeightfieldHeightDataPointer*)&parent = HeightfieldHeightDataPointer(); return true; } int size = glm::sqrt((float)maxSize); QByteArray contents(size * size, 0); int halfSize = size / 2; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer child = decodeInline(children[i]); + HeightfieldHeightDataPointer child = decodeInline(children[i]); if (!child) { continue; } @@ -789,7 +1139,7 @@ bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead) int xIndex = i & INDEX_MASK; const int Y_SHIFT = 1; int yIndex = (i >> Y_SHIFT) & INDEX_MASK; - if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { + if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { continue; // bottom is overriden by top } const int HALF_RANGE = 128; @@ -828,12 +1178,12 @@ bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead) } } } - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(new HeightfieldData(contents)); + *(HeightfieldHeightDataPointer*)&parent = HeightfieldHeightDataPointer(new HeightfieldHeightData(contents)); return false; } HeightfieldColorAttribute::HeightfieldColorAttribute(const QString& name) : - InlineAttribute(name) { + InlineAttribute(name) { } void HeightfieldColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { @@ -843,9 +1193,9 @@ void HeightfieldColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) c int size; in >> size; if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + *(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(); } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true)); + *(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(new HeightfieldColorData(in, size)); } } @@ -853,9 +1203,9 @@ void HeightfieldColorAttribute::write(Bitstream& out, void* value, bool isLeaf) if (!isLeaf) { return; } - HeightfieldDataPointer data = decodeInline(value); + HeightfieldColorDataPointer data = decodeInline(value); if (data) { - data->write(out, true); + data->write(out); } else { out << 0; } @@ -868,10 +1218,10 @@ void HeightfieldColorAttribute::readDelta(Bitstream& in, void*& value, void* ref int size; in >> size; if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + *(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(); } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData( - in, size, decodeInline(reference), true)); + *(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(new HeightfieldColorData( + in, size, decodeInline(reference))); } } @@ -879,9 +1229,9 @@ void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* re if (!isLeaf) { return; } - HeightfieldDataPointer data = decodeInline(value); + HeightfieldColorDataPointer data = decodeInline(value); if (data) { - data->writeDelta(out, decodeInline(reference), true); + data->writeDelta(out, decodeInline(reference)); } else { out << 0; } @@ -890,20 +1240,20 @@ void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* re bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool postRead) const { int maxSize = 0; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer pointer = decodeInline(children[i]); + HeightfieldColorDataPointer pointer = decodeInline(children[i]); if (pointer) { maxSize = qMax(maxSize, pointer->getContents().size()); } } if (maxSize == 0) { - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); + *(HeightfieldColorDataPointer*)&parent = HeightfieldColorDataPointer(); return true; } int size = glm::sqrt(maxSize / (float)HeightfieldData::COLOR_BYTES); QByteArray contents(size * size * HeightfieldData::COLOR_BYTES, 0); int halfSize = size / 2; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer child = decodeInline(children[i]); + HeightfieldColorDataPointer child = decodeInline(children[i]); if (!child) { continue; } @@ -913,7 +1263,7 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post int xIndex = i & INDEX_MASK; const int Y_SHIFT = 1; int yIndex = (i >> Y_SHIFT) & INDEX_MASK; - if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { + if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { continue; // bottom is overriden by top } int Z_SHIFT = 2; @@ -967,10 +1317,77 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post } } } - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(new HeightfieldData(contents)); + *(HeightfieldColorDataPointer*)&parent = HeightfieldColorDataPointer(new HeightfieldColorData(contents)); return false; } +HeightfieldTextureAttribute::HeightfieldTextureAttribute(const QString& name) : + InlineAttribute(name) { +} + +void HeightfieldTextureAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldTextureDataPointer*)&value = HeightfieldTextureDataPointer(); + } else { + *(HeightfieldTextureDataPointer*)&value = HeightfieldTextureDataPointer(new HeightfieldTextureData(in, size)); + } +} + +void HeightfieldTextureAttribute::write(Bitstream& out, void* value, bool isLeaf) const { + if (!isLeaf) { + return; + } + HeightfieldTextureDataPointer data = decodeInline(value); + if (data) { + data->write(out); + } else { + out << 0; + } +} + +void HeightfieldTextureAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldTextureDataPointer*)&value = HeightfieldTextureDataPointer(); + } else { + *(HeightfieldTextureDataPointer*)&value = HeightfieldTextureDataPointer(new HeightfieldTextureData( + in, size, decodeInline(reference))); + } +} + +void HeightfieldTextureAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + HeightfieldTextureDataPointer data = decodeInline(value); + if (data) { + data->writeDelta(out, decodeInline(reference)); + } else { + out << 0; + } +} + +bool HeightfieldTextureAttribute::merge(void*& parent, void* children[], bool postRead) const { + int maxSize = 0; + for (int i = 0; i < MERGE_COUNT; i++) { + HeightfieldTextureDataPointer pointer = decodeInline(children[i]); + if (pointer) { + maxSize = qMax(maxSize, pointer->getContents().size()); + } + } + *(HeightfieldTextureDataPointer*)&parent = HeightfieldTextureDataPointer(); + return maxSize == 0; +} + SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject, const SharedObjectPointer& defaultValue) : InlineAttribute(name, defaultValue), @@ -1082,9 +1499,7 @@ MetavoxelNode* SharedObjectSetAttribute::expandMetavoxelRoot(const MetavoxelNode MetavoxelNode* newGrandchild = new MetavoxelNode(attribute); newChild->setChild((index + j) % MetavoxelNode::CHILD_COUNT, newGrandchild); } - newChild->mergeChildren(attribute); } - newParent->mergeChildren(attribute); return newParent; } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index ddf6105662..fb6ff5097d 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "Bitstream.h" @@ -28,7 +29,10 @@ class QScriptEngine; class QScriptValue; class Attribute; +class HeightfieldColorData; class HeightfieldData; +class HeightfieldHeightData; +class HeightfieldTextureData; class MetavoxelData; class MetavoxelLOD; class MetavoxelNode; @@ -96,12 +100,15 @@ public: /// Returns a reference to the standard "spannerMask" attribute. const AttributePointer& getSpannerMaskAttribute() const { return _spannerMaskAttribute; } - /// Returns a reference to the standard HeightfieldPointer "heightfield" attribute. + /// Returns a reference to the standard HeightfieldDataPointer "heightfield" attribute. const AttributePointer& getHeightfieldAttribute() const { return _heightfieldAttribute; } - /// Returns a reference to the standard HeightfieldColorPointer "heightfieldColor" attribute. + /// Returns a reference to the standard HeightfieldDataPointer "heightfieldColor" attribute. const AttributePointer& getHeightfieldColorAttribute() const { return _heightfieldColorAttribute; } + /// Returns a reference to the standard HeightfieldDataPointer "heightfieldTexture" attribute. + const AttributePointer& getHeightfieldTextureAttribute() const { return _heightfieldTextureAttribute; } + private: static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine); @@ -119,6 +126,7 @@ private: AttributePointer _spannerMaskAttribute; AttributePointer _heightfieldAttribute; AttributePointer _heightfieldColorAttribute; + AttributePointer _heightfieldTextureAttribute; }; /// Converts a value to a void pointer. @@ -221,6 +229,11 @@ public: virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); } virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { write(out, value, isLeaf); } + virtual void readSubdivided(MetavoxelStreamState& state, void*& value, + const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const; + virtual void writeSubdivided(MetavoxelStreamState& state, void* value, + const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const; + virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; virtual void readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state); @@ -430,19 +443,20 @@ public: static const int COLOR_BYTES = 3; - HeightfieldData(const QByteArray& contents); - HeightfieldData(Bitstream& in, int bytes, bool color); - HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color); + HeightfieldData(const QByteArray& contents = QByteArray()); + virtual ~HeightfieldData(); const QByteArray& getContents() const { return _contents; } - void write(Bitstream& out, bool color); - void writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color); - -private: + void setDeltaData(const HeightfieldDataPointer& deltaData) { _deltaData = deltaData; } + const HeightfieldDataPointer& getDeltaData() const { return _deltaData; } - void read(Bitstream& in, int bytes, bool color); - void set(const QImage& image, bool color); + void setEncodedDelta(const QByteArray& encodedDelta) { _encodedDelta = encodedDelta; } + const QByteArray& getEncodedDelta() const { return _encodedDelta; } + + QMutex& getEncodedDeltaMutex() { return _encodedDeltaMutex; } + +protected: QByteArray _contents; QByteArray _encoded; @@ -451,10 +465,103 @@ private: HeightfieldDataPointer _deltaData; QByteArray _encodedDelta; QMutex _encodedDeltaMutex; + + class EncodedSubdivision { + public: + HeightfieldDataPointer ancestor; + QByteArray data; + }; + QVector _encodedSubdivisions; + QMutex _encodedSubdivisionsMutex; +}; + +typedef QExplicitlySharedDataPointer HeightfieldHeightDataPointer; + +/// Contains a block of heightfield height data. +class HeightfieldHeightData : public HeightfieldData { +public: + + HeightfieldHeightData(const QByteArray& contents); + HeightfieldHeightData(Bitstream& in, int bytes); + HeightfieldHeightData(Bitstream& in, int bytes, const HeightfieldHeightDataPointer& reference); + HeightfieldHeightData(Bitstream& in, int bytes, const HeightfieldHeightDataPointer& ancestor, + const glm::vec3& minimum, float size); + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldHeightDataPointer& reference); + void writeSubdivided(Bitstream& out, const HeightfieldHeightDataPointer& ancestor, + const glm::vec3& minimum, float size); + +private: + + void read(Bitstream& in, int bytes); + void set(const QImage& image); +}; + +typedef QExplicitlySharedDataPointer HeightfieldColorDataPointer; + +/// Contains a block of heightfield color data. +class HeightfieldColorData : public HeightfieldData { +public: + + HeightfieldColorData(const QByteArray& contents); + HeightfieldColorData(Bitstream& in, int bytes); + HeightfieldColorData(Bitstream& in, int bytes, const HeightfieldColorDataPointer& reference); + HeightfieldColorData(Bitstream& in, int bytes, const HeightfieldColorDataPointer& ancestor, + const glm::vec3& minimum, float size); + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldColorDataPointer& reference); + void writeSubdivided(Bitstream& out, const HeightfieldColorDataPointer& ancestor, + const glm::vec3& minimum, float size); + +private: + + void read(Bitstream& in, int bytes); + void set(const QImage& image); +}; + +typedef QExplicitlySharedDataPointer HeightfieldTextureDataPointer; + +/// Contains a block of heightfield texture data. +class HeightfieldTextureData : public HeightfieldData { +public: + + HeightfieldTextureData(const QByteArray& contents, + const QVector& textures = QVector()); + HeightfieldTextureData(Bitstream& in, int bytes); + HeightfieldTextureData(Bitstream& in, int bytes, const HeightfieldTextureDataPointer& reference); + + const QVector& getTextures() const { return _textures; } + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldTextureDataPointer& reference); + +private: + + void read(Bitstream& in, int bytes); + + QVector _textures; +}; + +/// Contains the description of a heightfield texture. +class HeightfieldTexture : public SharedObject { + Q_OBJECT + Q_PROPERTY(QUrl url MEMBER _url) + +public: + + Q_INVOKABLE HeightfieldTexture(); + + const QUrl& getURL() const { return _url; } + +private: + + QUrl _url; }; /// An attribute that stores heightfield data. -class HeightfieldAttribute : public InlineAttribute { +class HeightfieldAttribute : public InlineAttribute { Q_OBJECT public: @@ -471,7 +578,7 @@ public: }; /// An attribute that stores heightfield colors. -class HeightfieldColorAttribute : public InlineAttribute { +class HeightfieldColorAttribute : public InlineAttribute { Q_OBJECT public: @@ -487,6 +594,23 @@ public: virtual bool merge(void*& parent, void* children[], bool postRead = false) const; }; +/// An attribute that stores heightfield textures. +class HeightfieldTextureAttribute : public InlineAttribute { + Q_OBJECT + +public: + + Q_INVOKABLE HeightfieldTextureAttribute(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/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 3607441461..67fafe1633 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -998,7 +998,7 @@ MetavoxelNode* MetavoxelNode::readSubdivision(MetavoxelStreamState& state) { for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); newNode->_children[i] = new MetavoxelNode(state.base.attribute); - newNode->_children[i]->read(nextState); + newNode->_children[i]->readSubdivided(nextState, state, _attributeValue); } return newNode; } @@ -1037,7 +1037,7 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const { MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); - _children[i]->write(nextState); + _children[i]->writeSubdivided(nextState, state, _attributeValue); } } } else if (!leaf) { @@ -1051,6 +1051,46 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const { } } +void MetavoxelNode::readSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, + void* ancestorValue) { + clearChildren(state.base.attribute); + + if (!state.shouldSubdivide()) { + state.base.attribute->readSubdivided(state, _attributeValue, ancestorState, ancestorValue, true); + return; + } + bool leaf; + state.base.stream >> leaf; + state.base.attribute->readSubdivided(state, _attributeValue, ancestorState, ancestorValue, leaf); + if (!leaf) { + MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i] = new MetavoxelNode(state.base.attribute); + _children[i]->readSubdivided(nextState, ancestorState, ancestorValue); + } + mergeChildren(state.base.attribute, true); + } +} + +void MetavoxelNode::writeSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, + void* ancestorValue) const { + if (!state.shouldSubdivide()) { + state.base.attribute->writeSubdivided(state, _attributeValue, ancestorState, ancestorValue, true); + return; + } + bool leaf = isLeaf(); + state.base.stream << leaf; + state.base.attribute->writeSubdivided(state, _attributeValue, ancestorState, ancestorValue, leaf); + if (!leaf) { + MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->writeSubdivided(nextState, ancestorState, ancestorValue); + } + } +} + void MetavoxelNode::writeSpanners(MetavoxelStreamState& state) const { foreach (const SharedObjectPointer& object, decodeInline(_attributeValue)) { if (static_cast(object.data())->testAndSetVisited(state.base.visit)) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 9e5b2f04d1..8308c3c69b 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -225,6 +225,9 @@ public: MetavoxelNode* readSubdivision(MetavoxelStreamState& state); void writeSubdivision(MetavoxelStreamState& state) const; + void readSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, void* ancestorValue); + 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; diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index df6e8172e4..1a8f64d935 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -347,6 +347,8 @@ PaintHeightfieldHeightEditVisitor::PaintHeightfieldHeightEditVisitor(const Paint _bounds = Box(_edit.position - extents, _edit.position + extents); } +const int EIGHT_BIT_MAXIMUM = 255; + int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { if (!info.getBounds().intersects(_bounds)) { return STOP_RECURSION; @@ -354,7 +356,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { if (!info.isLeaf) { return DEFAULT_ORDER; } - HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); + HeightfieldHeightDataPointer pointer = info.inputValues.at(0).getInlineValue(); if (!pointer) { return STOP_RECURSION; } @@ -375,8 +377,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); uchar* lineDest = (uchar*)contents.data() + (int)z * size + (int)startX; float squaredRadius = scaledRadius * scaledRadius; - float squaredRadiusReciprocal = 1.0f / squaredRadius; - const int EIGHT_BIT_MAXIMUM = 255; + float squaredRadiusReciprocal = 1.0f / squaredRadius; float scaledHeight = _edit.height * EIGHT_BIT_MAXIMUM / info.size; bool changed = false; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { @@ -396,8 +397,8 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { lineDest += size; } if (changed) { - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + HeightfieldHeightDataPointer newPointer(new HeightfieldHeightData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); } return STOP_RECURSION; } @@ -435,25 +436,18 @@ PaintHeightfieldColorEditVisitor::PaintHeightfieldColorEditVisitor(const PaintHe _bounds = Box(_edit.position - extents, _edit.position + extents); } -int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { - if (!info.getBounds().intersects(_bounds)) { - return STOP_RECURSION; - } - if (!info.isLeaf) { - return DEFAULT_ORDER; - } - HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); +static void paintColor(MetavoxelInfo& info, int index, const glm::vec3& position, float radius, const QColor& color) { + HeightfieldColorDataPointer pointer = info.inputValues.at(index).getInlineValue(); if (!pointer) { - return STOP_RECURSION; + return; } QByteArray contents(pointer->getContents()); - const int BYTES_PER_PIXEL = 3; - int size = glm::sqrt((float)contents.size() / BYTES_PER_PIXEL); + int size = glm::sqrt((float)contents.size() / HeightfieldData::COLOR_BYTES); int highest = size - 1; float heightScale = size / info.size; - glm::vec3 center = (_edit.position - info.minimum) * heightScale; - float scaledRadius = _edit.radius * heightScale; + glm::vec3 center = (position - info.minimum) * heightScale; + float scaledRadius = radius * heightScale; glm::vec3 extents(scaledRadius, scaledRadius, scaledRadius); glm::vec3 start = glm::floor(center - extents); @@ -462,14 +456,14 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { // paint all points within the radius float z = qMax(start.z, 0.0f); float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); - int stride = size * BYTES_PER_PIXEL; - char* lineDest = contents.data() + (int)z * stride + (int)startX * BYTES_PER_PIXEL; + int stride = size * HeightfieldData::COLOR_BYTES; + char* lineDest = contents.data() + (int)z * stride + (int)startX * HeightfieldData::COLOR_BYTES; float squaredRadius = scaledRadius * scaledRadius; - char red = _edit.color.red(), green = _edit.color.green(), blue = _edit.color.blue(); + char red = color.red(), green = color.green(), blue = color.blue(); bool changed = false; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { char* dest = lineDest; - for (float x = startX; x <= endX; x += 1.0f, dest += BYTES_PER_PIXEL) { + for (float x = startX; x <= endX; x += 1.0f, dest += HeightfieldData::COLOR_BYTES) { float dx = x - center.x, dz = z - center.z; if (dx * dx + dz * dz <= squaredRadius) { dest[0] = red; @@ -481,9 +475,20 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { lineDest += stride; } if (changed) { - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + HeightfieldColorDataPointer newPointer(new HeightfieldColorData(contents)); + info.outputValues[index] = AttributeValue(info.inputValues.at(index).getAttribute(), + encodeInline(newPointer)); } +} + +int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + paintColor(info, 0, _edit.position, _edit.radius, _edit.color); return STOP_RECURSION; } @@ -492,3 +497,148 @@ void PaintHeightfieldColorEdit::apply(MetavoxelData& data, const WeakSharedObjec data.guide(visitor); } +PaintHeightfieldTextureEdit::PaintHeightfieldTextureEdit(const glm::vec3& position, float radius, + const SharedObjectPointer& texture, const QColor& averageColor) : + position(position), + radius(radius), + texture(texture), + averageColor(averageColor) { +} + +class PaintHeightfieldTextureEditVisitor : public MetavoxelVisitor { +public: + + PaintHeightfieldTextureEditVisitor(const PaintHeightfieldTextureEdit& edit); + + virtual int visit(MetavoxelInfo& info); + +private: + + PaintHeightfieldTextureEdit _edit; + Box _bounds; +}; + +PaintHeightfieldTextureEditVisitor::PaintHeightfieldTextureEditVisitor(const PaintHeightfieldTextureEdit& edit) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldTextureAttribute() << + AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector() << + AttributeRegistry::getInstance()->getHeightfieldTextureAttribute() << + AttributeRegistry::getInstance()->getHeightfieldColorAttribute()), + _edit(edit) { + + glm::vec3 extents(_edit.radius, _edit.radius, _edit.radius); + _bounds = Box(_edit.position - extents, _edit.position + extents); +} + +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; +} + +int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + HeightfieldTextureDataPointer pointer = info.inputValues.at(0).getInlineValue(); + if (!pointer) { + return STOP_RECURSION; + } + QVector textures = pointer->getTextures(); + QByteArray contents(pointer->getContents()); + uchar textureIndex = 0; + if (_edit.texture && static_cast(_edit.texture.data())->getURL().isValid()) { + // first look for a matching existing texture, noting the first reusable slot + int firstEmptyIndex = -1; + for (int i = 0; i < textures.size(); i++) { + const SharedObjectPointer& texture = textures.at(i); + if (texture) { + if (texture->equals(_edit.texture.data())) { + textureIndex = i + 1; + break; + } + } else if (firstEmptyIndex == -1) { + firstEmptyIndex = i; + } + } + // if nothing found, use the first empty slot or append + if (textureIndex == 0) { + if (firstEmptyIndex != -1) { + textures[firstEmptyIndex] = _edit.texture; + textureIndex = firstEmptyIndex + 1; + + } else if (textures.size() < EIGHT_BIT_MAXIMUM) { + textures.append(_edit.texture); + textureIndex = textures.size(); + + } else { + // last resort: find the least-used texture and remove it + QHash counts = countIndices(contents); + int lowestCount = INT_MAX; + for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { + if (it.value() < lowestCount) { + textureIndex = it.key(); + lowestCount = it.value(); + } + } + contents.replace((char)textureIndex, (char)0); + } + } + } + int size = glm::sqrt((float)contents.size()); + int highest = size - 1; + float heightScale = highest / info.size; + + glm::vec3 center = (_edit.position - info.minimum) * heightScale; + float scaledRadius = _edit.radius * heightScale; + glm::vec3 extents(scaledRadius, scaledRadius, scaledRadius); + + glm::vec3 start = glm::floor(center - extents); + glm::vec3 end = glm::ceil(center + extents); + + // paint all points within the radius + float z = qMax(start.z, 0.0f); + float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); + uchar* lineDest = (uchar*)contents.data() + (int)z * size + (int)startX; + float squaredRadius = scaledRadius * scaledRadius; + bool changed = false; + QHash counts; + for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { + uchar* dest = lineDest; + for (float x = startX; x <= endX; x += 1.0f, dest++) { + float dx = x - center.x, dz = z - center.z; + if (dx * dx + dz * dz <= squaredRadius) { + *dest = textureIndex; + changed = true; + } + } + lineDest += size; + } + if (changed) { + // clear any unused textures + QHash counts = countIndices(contents); + for (int i = 0; i < textures.size(); i++) { + if (counts.value(i + 1) == 0) { + textures[i] = SharedObjectPointer(); + } + } + while (!(textures.isEmpty() || textures.last())) { + textures.removeLast(); + } + HeightfieldTextureDataPointer newPointer(new HeightfieldTextureData(contents, textures)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + } + paintColor(info, 1, _edit.position, _edit.radius, _edit.averageColor); + return STOP_RECURSION; +} + +void PaintHeightfieldTextureEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + PaintHeightfieldTextureEditVisitor visitor(*this); + data.guide(visitor); +} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 2fc8cbf030..3d610b10df 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -241,4 +241,23 @@ public: DECLARE_STREAMABLE_METATYPE(PaintHeightfieldColorEdit) +/// An edit that sets a region of a heightfield texture. +class PaintHeightfieldTextureEdit : public MetavoxelEdit { + STREAMABLE + +public: + + STREAM glm::vec3 position; + STREAM float radius; + STREAM SharedObjectPointer texture; + STREAM QColor averageColor; + + PaintHeightfieldTextureEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, + const SharedObjectPointer& texture = SharedObjectPointer(), const QColor& averageColor = QColor()); + + virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; +}; + +DECLARE_STREAMABLE_METATYPE(PaintHeightfieldTextureEdit) + #endif // hifi_MetavoxelMessages_h diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index 053ef57bad..bf9b123a36 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -158,7 +158,7 @@ SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, bool nulla _type->addItem("(none)"); } foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(metaObject)) { - // add add constructable subclasses + // add constructable subclasses if (metaObject->constructorCount() > 0) { _type->addItem(metaObject->className(), QVariant::fromValue(metaObject)); } @@ -193,7 +193,9 @@ void SharedObjectEditor::detachObject() { for (int i = 0; i < form->rowCount(); i++) { QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget(); QMetaProperty property = metaObject->property(widget->property("propertyIndex").toInt()); - connect(_object.data(), signal(property.notifySignal().methodSignature()), SLOT(updateProperty())); + if (property.hasNotifySignal()) { + connect(_object.data(), signal(property.notifySignal().methodSignature()), SLOT(updateProperty())); + } } } @@ -226,6 +228,7 @@ void SharedObjectEditor::updateType() { const QMetaObject* metaObject = _type->itemData(_type->currentIndex()).value(); if (!metaObject) { _object.reset(); + emit objectChanged(_object); return; } QObject* newObject = metaObject->newInstance(); @@ -259,7 +262,7 @@ void SharedObjectEditor::updateType() { } } } - _object = static_cast(newObject); + emit objectChanged(_object = static_cast(newObject)); } void SharedObjectEditor::propertyChanged() { @@ -275,6 +278,7 @@ void SharedObjectEditor::propertyChanged() { QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType()); property.write(object, widget->property(valuePropertyName)); } + emit objectChanged(_object); } void SharedObjectEditor::updateProperty() { diff --git a/libraries/metavoxels/src/SharedObject.h b/libraries/metavoxels/src/SharedObject.h index 407fc820c8..157987ed6f 100644 --- a/libraries/metavoxels/src/SharedObject.h +++ b/libraries/metavoxels/src/SharedObject.h @@ -211,7 +211,7 @@ Q_DECLARE_METATYPE(SharedObjectSet) /// Allows editing shared object instances. class SharedObjectEditor : public QWidget { Q_OBJECT - Q_PROPERTY(SharedObjectPointer object READ getObject WRITE setObject USER true) + Q_PROPERTY(SharedObjectPointer object READ getObject WRITE setObject NOTIFY objectChanged USER true) public: @@ -222,6 +222,10 @@ public: /// "Detaches" the object pointer, copying it if anyone else is holding a reference. void detachObject(); +signals: + + void objectChanged(const SharedObjectPointer& object); + public slots: void setObject(const SharedObjectPointer& object); diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 1b48a2e333..619f652e1e 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -81,7 +81,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 1; + return 2; default: return 0; }