Merge pull request #3310 from ey6es/metavoxels

First cut at splatted terrain textures.
This commit is contained in:
Philip Rosedale 2014-08-22 08:27:16 -07:00
commit 6a340a1494
24 changed files with 1704 additions and 282 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<MetavoxelSystemClient*>(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<SharedObjectPointer>& 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<HeightfieldTexture*>(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<HeightfieldDataPointer>();
HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldHeightDataPointer>();
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<HeightfieldDataPointer>();
HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldColorDataPointer>();
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<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldColorAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldTextureAttribute() <<
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>() <<
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod),
regionBounds(glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX), glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX)),
@ -1149,14 +1342,14 @@ int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) {
return DEFAULT_ORDER;
}
HeightfieldBuffer* buffer = NULL;
HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldDataPointer>();
HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldHeightDataPointer>();
if (height) {
const QByteArray& heightContents = height->getContents();
int size = glm::sqrt(heightContents.size());
int extendedSize = size + HeightfieldBuffer::HEIGHT_EXTENSION;
int heightContentsSize = extendedSize * extendedSize;
HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldDataPointer>();
HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldColorDataPointer>();
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<HeightfieldTextureDataPointer>();
QByteArray textureContents;
QVector<SharedObjectPointer> textures;
if (texture) {
textureContents = texture->getContents();
textures = texture->getTextures();
}
const HeightfieldBuffer* existingBuffer = static_cast<const HeightfieldBuffer*>(
info.inputValues.at(2).getInlineValue<BufferDataPointer>().data());
info.inputValues.at(3).getInlineValue<BufferDataPointer>().data());
Box bounds = info.getBounds();
if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize &&
existingBuffer->getColor().size() == colorContentsSize) {
// we already have a buffer of the correct resolution
addRegion(bounds, existingBuffer->getHeightBounds());
return STOP_RECURSION;
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) {

View file

@ -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<SharedObjectPointer>& textures = QVector<SharedObjectPointer>());
~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<SharedObjectPointer>& getTextures() const { return _textures; }
QByteArray getUnextendedHeight() const;
QByteArray getUnextendedColor() const;
@ -173,13 +183,17 @@ private:
Box _colorBounds;
QByteArray _height;
QByteArray _color;
QByteArray _texture;
QVector<SharedObjectPointer> _textures;
GLuint _heightTextureID;
GLuint _colorTextureID;
GLuint _textureTextureID;
QVector<NetworkTexturePointer> _networkTextures;
int _heightSize;
float _heightIncrement;
int _colorSize;
float _colorIncrement;
typedef QPair<QOpenGLBuffer, QOpenGLBuffer> BufferPair;
static QHash<int, BufferPair> _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;
};

View file

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

View file

@ -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<NetworkTexture> 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<NetworkTexture>();
}
QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url);
NetworkTexturePointer texture = _dilatableNetworkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(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<Resource> TextureCache::createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
return QSharedPointer<Resource>(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content),
return QSharedPointer<Resource>(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)
{

View file

@ -23,6 +23,10 @@ class QOpenGLFramebufferObject;
class NetworkTexture;
typedef QSharedPointer<NetworkTexture> 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<NetworkTexture> 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.

View file

@ -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<HeightfieldBuffer*>(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<HeightfieldTexture*>(_textureEditor->getObject().data());
_texture = Application::getInstance()->getTextureCache()->getTexture(texture->getURL());
}

View file

@ -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<NetworkTexture> _texture;
};
#endif // hifi_MetavoxelEditor_h

View file

@ -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<SharedObjectPointer>& 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<HeightfieldDataPointer>(name) {
InlineAttribute<HeightfieldHeightDataPointer>(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<HeightfieldDataPointer>(value);
HeightfieldHeightDataPointer data = decodeInline<HeightfieldHeightDataPointer>(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<HeightfieldDataPointer>(reference), false));
*(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(new HeightfieldHeightData(
in, size, decodeInline<HeightfieldHeightDataPointer>(reference)));
}
}
@ -755,9 +1105,9 @@ void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* referen
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
HeightfieldHeightDataPointer data = decodeInline<HeightfieldHeightDataPointer>(value);
if (data) {
data->writeDelta(out, decodeInline<HeightfieldDataPointer>(reference), false);
data->writeDelta(out, decodeInline<HeightfieldHeightDataPointer>(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<HeightfieldDataPointer>(children[i]);
HeightfieldHeightDataPointer pointer = decodeInline<HeightfieldHeightDataPointer>(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<HeightfieldDataPointer>(children[i]);
HeightfieldHeightDataPointer child = decodeInline<HeightfieldHeightDataPointer>(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<HeightfieldDataPointer>(children[i | (1 << Y_SHIFT)])) {
if (yIndex == 0 && decodeInline<HeightfieldHeightDataPointer>(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<HeightfieldDataPointer>(name) {
InlineAttribute<HeightfieldColorDataPointer>(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<HeightfieldDataPointer>(value);
HeightfieldColorDataPointer data = decodeInline<HeightfieldColorDataPointer>(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<HeightfieldDataPointer>(reference), true));
*(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(new HeightfieldColorData(
in, size, decodeInline<HeightfieldColorDataPointer>(reference)));
}
}
@ -879,9 +1229,9 @@ void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* re
if (!isLeaf) {
return;
}
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
HeightfieldColorDataPointer data = decodeInline<HeightfieldColorDataPointer>(value);
if (data) {
data->writeDelta(out, decodeInline<HeightfieldDataPointer>(reference), true);
data->writeDelta(out, decodeInline<HeightfieldColorDataPointer>(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<HeightfieldDataPointer>(children[i]);
HeightfieldColorDataPointer pointer = decodeInline<HeightfieldColorDataPointer>(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<HeightfieldDataPointer>(children[i]);
HeightfieldColorDataPointer child = decodeInline<HeightfieldColorDataPointer>(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<HeightfieldDataPointer>(children[i | (1 << Y_SHIFT)])) {
if (yIndex == 0 && decodeInline<HeightfieldColorDataPointer>(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<HeightfieldTextureDataPointer>(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<HeightfieldTextureDataPointer>(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<HeightfieldTextureDataPointer>(reference)));
}
}
void HeightfieldTextureAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const {
if (!isLeaf) {
return;
}
HeightfieldTextureDataPointer data = decodeInline<HeightfieldTextureDataPointer>(value);
if (data) {
data->writeDelta(out, decodeInline<HeightfieldTextureDataPointer>(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<HeightfieldTextureDataPointer>(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<SharedObjectPointer>(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;
}

View file

@ -18,6 +18,7 @@
#include <QReadWriteLock>
#include <QSharedPointer>
#include <QString>
#include <QUrl>
#include <QWidget>
#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<EncodedSubdivision> _encodedSubdivisions;
QMutex _encodedSubdivisionsMutex;
};
typedef QExplicitlySharedDataPointer<HeightfieldHeightData> 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<HeightfieldColorData> 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<HeightfieldTextureData> HeightfieldTextureDataPointer;
/// Contains a block of heightfield texture data.
class HeightfieldTextureData : public HeightfieldData {
public:
HeightfieldTextureData(const QByteArray& contents,
const QVector<SharedObjectPointer>& textures = QVector<SharedObjectPointer>());
HeightfieldTextureData(Bitstream& in, int bytes);
HeightfieldTextureData(Bitstream& in, int bytes, const HeightfieldTextureDataPointer& reference);
const QVector<SharedObjectPointer>& getTextures() const { return _textures; }
void write(Bitstream& out);
void writeDelta(Bitstream& out, const HeightfieldTextureDataPointer& reference);
private:
void read(Bitstream& in, int bytes);
QVector<SharedObjectPointer> _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<HeightfieldDataPointer> {
class HeightfieldAttribute : public InlineAttribute<HeightfieldHeightDataPointer> {
Q_OBJECT
public:
@ -471,7 +578,7 @@ public:
};
/// An attribute that stores heightfield colors.
class HeightfieldColorAttribute : public InlineAttribute<HeightfieldDataPointer> {
class HeightfieldColorAttribute : public InlineAttribute<HeightfieldColorDataPointer> {
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<HeightfieldTextureDataPointer> {
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<SharedObjectPointer> {
Q_OBJECT

View file

@ -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<SharedObjectSet>(_attributeValue)) {
if (static_cast<Spanner*>(object.data())->testAndSetVisited(state.base.visit)) {

View file

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

View file

@ -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<HeightfieldDataPointer>();
HeightfieldHeightDataPointer pointer = info.inputValues.at(0).getInlineValue<HeightfieldHeightDataPointer>();
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<HeightfieldDataPointer>(newPointer));
HeightfieldHeightDataPointer newPointer(new HeightfieldHeightData(contents));
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldHeightDataPointer>(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<HeightfieldDataPointer>();
static void paintColor(MetavoxelInfo& info, int index, const glm::vec3& position, float radius, const QColor& color) {
HeightfieldColorDataPointer pointer = info.inputValues.at(index).getInlineValue<HeightfieldColorDataPointer>();
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<HeightfieldDataPointer>(newPointer));
HeightfieldColorDataPointer newPointer(new HeightfieldColorData(contents));
info.outputValues[index] = AttributeValue(info.inputValues.at(index).getAttribute(),
encodeInline<HeightfieldColorDataPointer>(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<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldTextureAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector<AttributePointer>() <<
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<uchar, int> countIndices(const QByteArray& contents) {
QHash<uchar, int> 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<HeightfieldTextureDataPointer>();
if (!pointer) {
return STOP_RECURSION;
}
QVector<SharedObjectPointer> textures = pointer->getTextures();
QByteArray contents(pointer->getContents());
uchar textureIndex = 0;
if (_edit.texture && static_cast<HeightfieldTexture*>(_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<uchar, int> counts = countIndices(contents);
int lowestCount = INT_MAX;
for (QHash<uchar, int>::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<uchar, int> 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<uchar, int> 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<HeightfieldTextureDataPointer>(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);
}

View file

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

View file

@ -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<const QMetaObject*>();
if (!metaObject) {
_object.reset();
emit objectChanged(_object);
return;
}
QObject* newObject = metaObject->newInstance();
@ -259,7 +262,7 @@ void SharedObjectEditor::updateType() {
}
}
}
_object = static_cast<SharedObject*>(newObject);
emit objectChanged(_object = static_cast<SharedObject*>(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() {

View file

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

View file

@ -81,7 +81,7 @@ PacketVersion versionForPacketType(PacketType type) {
case PacketTypeAudioStreamStats:
return 1;
case PacketTypeMetavoxelData:
return 1;
return 2;
default:
return 0;
}