mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 12:04:18 +02:00
Merge pull request #3310 from ey6es/metavoxels
First cut at splatted terrain textures.
This commit is contained in:
commit
6a340a1494
24 changed files with 1704 additions and 282 deletions
20
interface/resources/shaders/metavoxel_heightfield_base.frag
Normal file
20
interface/resources/shaders/metavoxel_heightfield_base.frag
Normal 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);
|
||||
}
|
33
interface/resources/shaders/metavoxel_heightfield_base.vert
Normal file
33
interface/resources/shaders/metavoxel_heightfield_base.vert
Normal 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;
|
||||
}
|
21
interface/resources/shaders/metavoxel_heightfield_light.frag
Normal file
21
interface/resources/shaders/metavoxel_heightfield_light.frag
Normal 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)));
|
||||
}
|
45
interface/resources/shaders/metavoxel_heightfield_light.vert
Normal file
45
interface/resources/shaders/metavoxel_heightfield_light.vert
Normal 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);
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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));
|
||||
}
|
28
interface/resources/shaders/metavoxel_heightfield_splat.frag
Normal file
28
interface/resources/shaders/metavoxel_heightfield_splat.frag
Normal 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);
|
||||
}
|
50
interface/resources/shaders/metavoxel_heightfield_splat.vert
Normal file
50
interface/resources/shaders/metavoxel_heightfield_splat.vert
Normal 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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -81,7 +81,7 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
case PacketTypeAudioStreamStats:
|
||||
return 1;
|
||||
case PacketTypeMetavoxelData:
|
||||
return 1;
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue