Merge pull request #3621 from ey6es/metavoxels

Sculpt brush, very rough conversion of heightfield patches to dual contour surfaces on edit.
This commit is contained in:
AndrewMeadows 2014-10-20 08:55:01 -07:00
commit 2e958599aa
9 changed files with 784 additions and 283 deletions

View file

@ -1,7 +1,7 @@
#version 120
//
// metavoxel_voxel_cursor.frag
// metavoxel_cursor.frag
// fragment shader
//
// Created by Andrzej Kapolka on 10/10/14.

View file

@ -1,32 +0,0 @@
#version 120
//
// metavoxel_heightfield_cursor.frag
// fragment shader
//
// Created by Andrzej Kapolka on 8/7/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 inner radius of the outline, squared
const float SQUARED_OUTLINE_INNER_RADIUS = 0.81;
// the outer radius of the outline, squared
const float SQUARED_OUTLINE_OUTER_RADIUS = 1.0;
// the inner radius of the inset, squared
const float SQUARED_INSET_INNER_RADIUS = 0.855625;
// the outer radius of the inset, squared
const float SQUARED_INSET_OUTER_RADIUS = 0.950625;
void main(void) {
// use the distance to compute the ring color, then multiply it by the varying color
float squaredDistance = dot(gl_TexCoord[0].st, gl_TexCoord[0].st);
float alpha = step(SQUARED_OUTLINE_INNER_RADIUS, squaredDistance) * step(squaredDistance, SQUARED_OUTLINE_OUTER_RADIUS);
float white = step(SQUARED_INSET_INNER_RADIUS, squaredDistance) * step(squaredDistance, SQUARED_INSET_OUTER_RADIUS);
gl_FragColor = gl_Color * vec4(white, white, white, alpha);
}

View file

@ -21,7 +21,8 @@ void main(void) {
gl_Position = gl_ProjectionMatrix * viewPosition;
// generate the texture coordinates from the view position
gl_TexCoord[0] = vec4(dot(viewPosition, gl_EyePlaneS[4]), dot(viewPosition, gl_EyePlaneT[4]), 0.0, 1.0);
gl_TexCoord[0] = vec4(dot(viewPosition, gl_EyePlaneS[4]), dot(viewPosition, gl_EyePlaneT[4]),
dot(viewPosition, gl_EyePlaneR[4]), 1.0);
// the zero height should be invisible
gl_FrontColor = vec4(1.0, 1.0, 1.0, 1.0 - step(height, 0.0));

View file

@ -533,8 +533,10 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r
float scale = 1.0f / radius;
glm::vec4 sCoefficients(scale, 0.0f, 0.0f, -scale * position.x);
glm::vec4 tCoefficients(0.0f, 0.0f, scale, -scale * position.z);
glm::vec4 rCoefficients(0.0f, 0.0f, 0.0f, 0.0f);
glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&sCoefficients);
glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&tCoefficients);
glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&rCoefficients);
glActiveTexture(GL_TEXTURE0);
glm::vec3 extents(radius, radius, radius);
@ -575,12 +577,23 @@ void MetavoxelSystem::renderVoxelCursor(const glm::vec3& position, float radius)
glActiveTexture(GL_TEXTURE0);
glm::vec3 extents(radius, radius, radius);
CursorRenderVisitor visitor(Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(),
Box(position - extents, position + extents));
guideToAugmented(visitor);
Box bounds(position - extents, position + extents);
CursorRenderVisitor voxelVisitor(Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(), bounds);
guideToAugmented(voxelVisitor);
DefaultMetavoxelRendererImplementation::getVoxelCursorProgram().release();
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
DefaultMetavoxelRendererImplementation::getHeightfieldCursorProgram().bind();
CursorRenderVisitor heightfieldVisitor(Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(),
bounds);
guideToAugmented(heightfieldVisitor);
DefaultMetavoxelRendererImplementation::getHeightfieldCursorProgram().release();
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisable(GL_POLYGON_OFFSET_FILL);
@ -1450,7 +1463,7 @@ void DefaultMetavoxelRendererImplementation::init() {
_heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
"shaders/metavoxel_heightfield_cursor.vert");
_heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
"shaders/metavoxel_heightfield_cursor.frag");
"shaders/metavoxel_cursor.frag");
_heightfieldCursorProgram.link();
_heightfieldCursorProgram.bind();
@ -1468,7 +1481,7 @@ void DefaultMetavoxelRendererImplementation::init() {
_voxelCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
"shaders/metavoxel_voxel_cursor.vert");
_voxelCursorProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
"shaders/metavoxel_voxel_cursor.frag");
"shaders/metavoxel_cursor.frag");
_voxelCursorProgram.link();
}
}

View file

@ -130,6 +130,7 @@ MetavoxelEditor::MetavoxelEditor() :
addTool(new VoxelMaterialBoxTool(this));
addTool(new VoxelMaterialSpannerTool(this));
addTool(new VoxelMaterialBrushTool(this));
addTool(new VoxelSculptBrushTool(this));
updateAttributes();
@ -1048,7 +1049,7 @@ void ImportHeightfieldTool::apply() {
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue(
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer))));
int size = glm::sqrt(float(height.size())) + HeightfieldBuffer::SHARED_EDGE;
int size = glm::sqrt(float(height.size()));
QByteArray material(size * size, 0);
HeightfieldMaterialDataPointer materialPointer(new HeightfieldMaterialData(material));
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), new MetavoxelNode(AttributeValue(
@ -1294,35 +1295,44 @@ QVariant HeightfieldHeightBrushTool::createEdit(bool alternate) {
alternate ? -_height->value() : _height->value()));
}
HeightfieldMaterialBrushTool::HeightfieldMaterialBrushTool(MetavoxelEditor* editor) :
HeightfieldBrushTool(editor, "Material Brush") {
MaterialControl::MaterialControl(QWidget* widget, QFormLayout* form, bool clearable) :
QObject(widget) {
_form->addRow("Color:", _color = new QColorEditor(this));
connect(_color, &QColorEditor::colorChanged, this, &HeightfieldMaterialBrushTool::clearTexture);
_form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &HeightfieldMaterialBrushTool::updateTexture);
QHBoxLayout* colorLayout = new QHBoxLayout();
form->addRow(colorLayout);
colorLayout->addWidget(new QLabel("Color:"));
colorLayout->addWidget(_color = new QColorEditor(widget), 1);
connect(_color, &QColorEditor::colorChanged, this, &MaterialControl::clearTexture);
if (clearable) {
QPushButton* eraseButton = new QPushButton("Erase");
colorLayout->addWidget(eraseButton);
connect(eraseButton, &QPushButton::clicked, this, &MaterialControl::clearColor);
}
form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &MaterialControl::updateTexture);
}
QVariant HeightfieldMaterialBrushTool::createEdit(bool alternate) {
if (alternate) {
return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), SharedObjectPointer(), QColor()));
SharedObjectPointer MaterialControl::getMaterial() {
SharedObjectPointer material = _materialEditor->getObject();
if (static_cast<MaterialObject*>(material.data())->getDiffuse().isValid()) {
_materialEditor->detachObject();
} else {
SharedObjectPointer material = _materialEditor->getObject();
if (static_cast<MaterialObject*>(material.data())->getDiffuse().isValid()) {
_materialEditor->detachObject();
} else {
material = SharedObjectPointer();
}
return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), material,
_color->getColor()));
}
material = SharedObjectPointer();
}
return material;
}
void HeightfieldMaterialBrushTool::clearTexture() {
void MaterialControl::clearColor() {
_color->setColor(QColor(0, 0, 0, 0));
clearTexture();
}
void MaterialControl::clearTexture() {
_materialEditor->setObject(new MaterialObject());
}
void HeightfieldMaterialBrushTool::updateTexture() {
void MaterialControl::updateTexture() {
if (_texture) {
_texture->disconnect(this);
}
@ -1336,15 +1346,29 @@ void HeightfieldMaterialBrushTool::updateTexture() {
if (_texture->isLoaded()) {
textureLoaded();
} else {
connect(_texture.data(), &Resource::loaded, this, &HeightfieldMaterialBrushTool::textureLoaded);
connect(_texture.data(), &Resource::loaded, this, &MaterialControl::textureLoaded);
}
}
}
void HeightfieldMaterialBrushTool::textureLoaded() {
void MaterialControl::textureLoaded() {
_color->setColor(_texture->getAverageColor());
}
HeightfieldMaterialBrushTool::HeightfieldMaterialBrushTool(MetavoxelEditor* editor) :
HeightfieldBrushTool(editor, "Material Brush"),
_materialControl(new MaterialControl(this, _form)) {
}
QVariant HeightfieldMaterialBrushTool::createEdit(bool alternate) {
if (alternate) {
return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), SharedObjectPointer(), QColor()));
} else {
return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), _materialControl->getMaterial(),
_materialControl->getColor()));
}
}
VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) :
BoxTool(editor, "Set Voxel Material (Box)", false) {
@ -1360,17 +1384,7 @@ VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) :
form->addRow(gridLayout);
_snapToGrid->setChecked(true);
QHBoxLayout* colorLayout = new QHBoxLayout();
form->addRow(colorLayout);
colorLayout->addWidget(new QLabel("Color:"));
colorLayout->addWidget(_color = new QColorEditor(this), 1);
connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialBoxTool::clearTexture);
QPushButton* eraseButton = new QPushButton("Erase");
colorLayout->addWidget(eraseButton);
connect(eraseButton, &QPushButton::clicked, this, &VoxelMaterialBoxTool::clearColor);
form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialBoxTool::updateTexture);
_materialControl = new MaterialControl(this, form, true);
}
bool VoxelMaterialBoxTool::appliesTo(const AttributePointer& attribute) const {
@ -1382,16 +1396,10 @@ bool VoxelMaterialBoxTool::shouldSnapToGrid() {
}
QColor VoxelMaterialBoxTool::getColor() {
return _color->getColor();
return _materialControl->getColor();
}
void VoxelMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
SharedObjectPointer material = _materialEditor->getObject();
if (static_cast<MaterialObject*>(material.data())->getDiffuse().isValid()) {
_materialEditor->detachObject();
} else {
material = SharedObjectPointer();
}
Cuboid* cuboid = new Cuboid();
cuboid->setTranslation((maximum + minimum) * 0.5f);
glm::vec3 vector = (maximum - minimum) * 0.5f;
@ -1399,42 +1407,10 @@ void VoxelMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3&
cuboid->setAspectY(vector.y / vector.x);
cuboid->setAspectZ(vector.z / vector.x);
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(SharedObjectPointer(cuboid),
material, _color->getColor())) };
_materialControl->getMaterial(), _materialControl->getColor())) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
void VoxelMaterialBoxTool::clearColor() {
_color->setColor(QColor(0, 0, 0, 0));
clearTexture();
}
void VoxelMaterialBoxTool::clearTexture() {
_materialEditor->setObject(new MaterialObject());
}
void VoxelMaterialBoxTool::updateTexture() {
if (_texture) {
_texture->disconnect(this);
}
MaterialObject* material = static_cast<MaterialObject*>(_materialEditor->getObject().data());
if (!material->getDiffuse().isValid()) {
_texture.clear();
return;
}
_texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE);
if (_texture) {
if (_texture->isLoaded()) {
textureLoaded();
} else {
connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialBoxTool::textureLoaded);
}
}
}
void VoxelMaterialBoxTool::textureLoaded() {
_color->setColor(_texture->getAverageColor());
}
VoxelMaterialSpannerTool::VoxelMaterialSpannerTool(MetavoxelEditor* editor) :
PlaceSpannerTool(editor, "Set Voxel Material (Spanner)", QString(), false) {
@ -1446,17 +1422,7 @@ VoxelMaterialSpannerTool::VoxelMaterialSpannerTool(MetavoxelEditor* editor) :
form->addRow(_spannerEditor = new SharedObjectEditor(&Spanner::staticMetaObject, false, this));
_spannerEditor->setObject(new Sphere());
QHBoxLayout* colorLayout = new QHBoxLayout();
form->addRow(colorLayout);
colorLayout->addWidget(new QLabel("Color:"));
colorLayout->addWidget(_color = new QColorEditor(this), 1);
connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialSpannerTool::clearTexture);
QPushButton* eraseButton = new QPushButton("Erase");
colorLayout->addWidget(eraseButton);
connect(eraseButton, &QPushButton::clicked, this, &VoxelMaterialSpannerTool::clearColor);
form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialSpannerTool::updateTexture);
_materialControl = new MaterialControl(this, form, true);
QPushButton* place = new QPushButton("Set");
layout()->addWidget(place);
@ -1475,54 +1441,16 @@ SharedObjectPointer VoxelMaterialSpannerTool::getSpanner(bool detach) {
}
QColor VoxelMaterialSpannerTool::getColor() {
return _color->getColor();
return _materialControl->getColor();
}
void VoxelMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) {
_spannerEditor->detachObject();
SharedObjectPointer material = _materialEditor->getObject();
if (static_cast<MaterialObject*>(material.data())->getDiffuse().isValid()) {
_materialEditor->detachObject();
} else {
material = SharedObjectPointer();
}
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(spanner, material, _color->getColor())) };
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(spanner,
_materialControl->getMaterial(), _materialControl->getColor())) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
void VoxelMaterialSpannerTool::clearColor() {
_color->setColor(QColor(0, 0, 0, 0));
clearTexture();
}
void VoxelMaterialSpannerTool::clearTexture() {
_materialEditor->setObject(new MaterialObject());
}
void VoxelMaterialSpannerTool::updateTexture() {
if (_texture) {
_texture->disconnect(this);
}
MaterialObject* material = static_cast<MaterialObject*>(_materialEditor->getObject().data());
if (!material->getDiffuse().isValid()) {
_texture.clear();
return;
}
_texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE);
if (_texture) {
if (_texture->isLoaded()) {
textureLoaded();
} else {
connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialSpannerTool::textureLoaded);
}
}
}
void VoxelMaterialSpannerTool::textureLoaded() {
_color->setColor(_texture->getAverageColor());
}
VoxelBrushTool::VoxelBrushTool(MetavoxelEditor* editor, const QString& name) :
MetavoxelTool(editor, name, false, true) {
@ -1549,12 +1477,14 @@ void VoxelBrushTool::render() {
glm::vec3 origin = Application::getInstance()->getMouseRayOrigin();
glm::vec3 direction = Application::getInstance()->getMouseRayDirection();
float distance;
if (!Application::getInstance()->getMetavoxels()->findFirstRayVoxelIntersection(origin, direction, distance)) {
float heightfieldDistance = FLT_MAX, voxelDistance = FLT_MAX;
if (!(Application::getInstance()->getMetavoxels()->findFirstRayHeightfieldIntersection(
origin, direction, heightfieldDistance) |
Application::getInstance()->getMetavoxels()->findFirstRayVoxelIntersection(origin, direction, voxelDistance))) {
return;
}
Application::getInstance()->getMetavoxels()->renderVoxelCursor(
_position = origin + distance * direction, _radius->value());
_position = origin + qMin(heightfieldDistance, voxelDistance) * direction, _radius->value());
}
bool VoxelBrushTool::eventFilter(QObject* watched, QEvent* event) {
@ -1573,51 +1503,33 @@ bool VoxelBrushTool::eventFilter(QObject* watched, QEvent* event) {
}
VoxelMaterialBrushTool::VoxelMaterialBrushTool(MetavoxelEditor* editor) :
VoxelBrushTool(editor, "Material Brush") {
_form->addRow("Color:", _color = new QColorEditor(this));
connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialBrushTool::clearTexture);
_form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialBrushTool::updateTexture);
VoxelBrushTool(editor, "Material Brush"),
_materialControl(new MaterialControl(this, _form)) {
}
QVariant VoxelMaterialBrushTool::createEdit(bool alternate) {
if (alternate) {
return QVariant::fromValue(PaintVoxelMaterialEdit(_position, _radius->value(), SharedObjectPointer(), QColor()));
} else {
SharedObjectPointer material = _materialEditor->getObject();
if (static_cast<MaterialObject*>(material.data())->getDiffuse().isValid()) {
_materialEditor->detachObject();
} else {
material = SharedObjectPointer();
}
return QVariant::fromValue(PaintVoxelMaterialEdit(_position, _radius->value(), material, _color->getColor()));
return QVariant::fromValue(PaintVoxelMaterialEdit(_position, _radius->value(),
_materialControl->getMaterial(), _materialControl->getColor()));
}
}
void VoxelMaterialBrushTool::clearTexture() {
_materialEditor->setObject(new MaterialObject());
VoxelSculptBrushTool::VoxelSculptBrushTool(MetavoxelEditor* editor) :
VoxelBrushTool(editor, "Sculpt Brush"),
_materialControl(new MaterialControl(this, _form, true)) {
}
void VoxelMaterialBrushTool::updateTexture() {
if (_texture) {
_texture->disconnect(this);
}
MaterialObject* material = static_cast<MaterialObject*>(_materialEditor->getObject().data());
if (!material->getDiffuse().isValid()) {
_texture.clear();
return;
}
_texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE);
if (_texture) {
if (_texture->isLoaded()) {
textureLoaded();
} else {
connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialBrushTool::textureLoaded);
}
QVariant VoxelSculptBrushTool::createEdit(bool alternate) {
Sphere* sphere = new Sphere();
sphere->setTranslation(_position);
sphere->setScale(_radius->value());
if (alternate) {
return QVariant::fromValue(VoxelMaterialSpannerEdit(SharedObjectPointer(sphere),
SharedObjectPointer(), QColor(0, 0, 0, 0)));
} else {
return QVariant::fromValue(VoxelMaterialSpannerEdit(SharedObjectPointer(sphere),
_materialControl->getMaterial(), _materialControl->getColor()));
}
}
void VoxelMaterialBrushTool::textureLoaded() {
_color->setColor(_texture->getAverageColor());
}

View file

@ -380,6 +380,32 @@ private:
QDoubleSpinBox* _height;
};
/// Contains widgets for editing materials.
class MaterialControl : public QObject {
Q_OBJECT
public:
MaterialControl(QWidget* widget, QFormLayout* form, bool clearable = false);
SharedObjectPointer getMaterial();
const QColor& getColor() const { return _color->getColor(); }
private slots:
void clearColor();
void clearTexture();
void updateTexture();
void textureLoaded();
private:
QColorEditor* _color;
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
};
/// Allows texturing parts of the heightfield.
class HeightfieldMaterialBrushTool : public HeightfieldBrushTool {
Q_OBJECT
@ -391,18 +417,10 @@ public:
protected:
virtual QVariant createEdit(bool alternate);
private slots:
void clearTexture();
void updateTexture();
void textureLoaded();
private:
QColorEditor* _color;
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
MaterialControl* _materialControl;
};
/// Allows setting voxel materials by dragging out a box.
@ -423,19 +441,10 @@ protected:
virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
private slots:
void clearColor();
void clearTexture();
void updateTexture();
void textureLoaded();
private:
QCheckBox* _snapToGrid;
QColorEditor* _color;
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
MaterialControl* _materialControl;
};
/// Allows setting voxel materials by placing a spanner.
@ -454,19 +463,10 @@ protected:
virtual QColor getColor();
virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner);
private slots:
void clearColor();
void clearTexture();
void updateTexture();
void textureLoaded();
private:
SharedObjectEditor* _spannerEditor;
QColorEditor* _color;
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
MaterialControl* _materialControl;
};
/// Base class for voxel brush tools.
@ -505,17 +505,26 @@ protected:
virtual QVariant createEdit(bool alternate);
private slots:
void clearTexture();
void updateTexture();
void textureLoaded();
private:
QColorEditor* _color;
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
MaterialControl* _materialControl;
};
/// Allows sculpting parts of the voxel field.
class VoxelSculptBrushTool : public VoxelBrushTool {
Q_OBJECT
public:
VoxelSculptBrushTool(MetavoxelEditor* editor);
protected:
virtual QVariant createEdit(bool alternate);
private:
MaterialControl* _materialControl;
};
#endif // hifi_MetavoxelEditor_h

View file

@ -2045,6 +2045,27 @@ bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& dire
return _bounds.findRayIntersection(origin, direction, distance);
}
bool Spanner::hasOwnColors() const {
return false;
}
bool Spanner::hasOwnMaterials() const {
return false;
}
QRgb Spanner::getColor(const glm::vec3& point) {
return 0;
}
int Spanner::getMaterial(const glm::vec3& point) {
return 0;
}
QVector<SharedObjectPointer>& Spanner::getMaterials() {
static QVector<SharedObjectPointer> emptyMaterials;
return emptyMaterials;
}
bool Spanner::contains(const glm::vec3& point) {
return false;
}
@ -2365,3 +2386,234 @@ bool StaticModel::findRayIntersection(const glm::vec3& origin, const glm::vec3&
QByteArray StaticModel::getRendererClassName() const {
return "StaticModelRenderer";
}
const float EIGHT_BIT_MAXIMUM = 255.0f;
Heightfield::Heightfield(const Box& bounds, float increment, const QByteArray& height, const QByteArray& color,
const QByteArray& material, const QVector<SharedObjectPointer>& materials) :
_increment(increment),
_width((int)glm::round((bounds.maximum.x - bounds.minimum.x) / increment) + 1),
_heightScale((bounds.maximum.y - bounds.minimum.y) / EIGHT_BIT_MAXIMUM),
_height(height),
_color(color),
_material(material),
_materials(materials) {
setBounds(bounds);
}
bool Heightfield::hasOwnColors() const {
return true;
}
bool Heightfield::hasOwnMaterials() const {
return true;
}
QRgb Heightfield::getColor(const glm::vec3& point) {
glm::vec3 relative = (point - getBounds().minimum) / _increment;
glm::vec3 floors = glm::floor(relative);
glm::vec3 ceils = glm::ceil(relative);
glm::vec3 fracts = glm::fract(relative);
int floorX = (int)floors.x;
int floorZ = (int)floors.z;
int ceilX = (int)ceils.x;
int ceilZ = (int)ceils.z;
const uchar* src = (const uchar*)_color.constData();
const uchar* upperLeft = src + (floorZ * _width + floorX) * DataBlock::COLOR_BYTES;
const uchar* lowerRight = src + (ceilZ * _width + ceilX) * DataBlock::COLOR_BYTES;
glm::vec3 interpolatedColor = glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]),
glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z);
// the final vertex (and thus which triangle we check) depends on which half we're on
if (fracts.x >= fracts.z) {
const uchar* upperRight = src + (floorZ * _width + ceilX) * DataBlock::COLOR_BYTES;
interpolatedColor = glm::mix(interpolatedColor, glm::mix(glm::vec3(upperRight[0], upperRight[1], upperRight[2]),
glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z), (fracts.x - fracts.z) / (1.0f - fracts.z));
} else {
const uchar* lowerLeft = src + (ceilZ * _width + floorX) * DataBlock::COLOR_BYTES;
interpolatedColor = glm::mix(glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]),
glm::vec3(lowerLeft[0], lowerLeft[1], lowerLeft[2]), fracts.z), interpolatedColor, fracts.x / fracts.z);
}
return qRgb(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b);
}
int Heightfield::getMaterial(const glm::vec3& point) {
glm::vec3 relative = (point - getBounds().minimum) / _increment;
const uchar* src = (const uchar*)_material.constData();
return src[(int)glm::round(relative.z) * _width + (int)glm::round(relative.x)];
}
QVector<SharedObjectPointer>& Heightfield::getMaterials() {
return _materials;
}
bool Heightfield::contains(const glm::vec3& point) {
if (!getBounds().contains(point)) {
return false;
}
glm::vec3 relative = (point - getBounds().minimum) / _increment;
glm::vec3 floors = glm::floor(relative);
glm::vec3 ceils = glm::ceil(relative);
glm::vec3 fracts = glm::fract(relative);
int floorX = (int)floors.x;
int floorZ = (int)floors.z;
int ceilX = (int)ceils.x;
int ceilZ = (int)ceils.z;
const uchar* src = (const uchar*)_height.constData();
float upperLeft = src[floorZ * _width + floorX];
float lowerRight = src[ceilZ * _width + ceilX];
float interpolatedHeight = glm::mix(upperLeft, lowerRight, fracts.z);
// the final vertex (and thus which triangle we check) depends on which half we're on
if (fracts.x >= fracts.z) {
float upperRight = src[floorZ * _width + ceilX];
interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z),
(fracts.x - fracts.z) / (1.0f - fracts.z));
} else {
float lowerLeft = src[ceilZ * _width + floorX];
interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, fracts.x / fracts.z);
}
return interpolatedHeight != 0.0f && point.y <= interpolatedHeight * _heightScale + getBounds().minimum.y;
}
bool Heightfield::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) {
// find the initial location in heightfield coordinates
float rayDistance;
glm::vec3 direction = end - start;
if (!getBounds().findRayIntersection(start, direction, rayDistance) || rayDistance > 1.0f) {
return false;
}
glm::vec3 entry = (start + direction * rayDistance - getBounds().minimum) / _increment;
direction /= _increment;
glm::vec3 floors = glm::floor(entry);
glm::vec3 ceils = glm::ceil(entry);
if (floors.x == ceils.x) {
if (direction.x > 0.0f) {
ceils.x += 1.0f;
} else {
floors.x -= 1.0f;
}
}
if (floors.z == ceils.z) {
if (direction.z > 0.0f) {
ceils.z += 1.0f;
} else {
floors.z -= 1.0f;
}
}
bool withinBounds = true;
float accumulatedDistance = 0.0f;
const uchar* src = (const uchar*)_height.constData();
int highestX = _width - 1;
float highestY = (getBounds().maximum.y - getBounds().minimum.y) / _increment;
int highestZ = (int)glm::round((getBounds().maximum.z - getBounds().minimum.z) / _increment);
float heightScale = _heightScale / _increment;
while (withinBounds && accumulatedDistance <= 1.0f) {
// find the heights at the corners of the current cell
int floorX = qMin(qMax((int)floors.x, 0), highestX);
int floorZ = qMin(qMax((int)floors.z, 0), highestZ);
int ceilX = qMin(qMax((int)ceils.x, 0), highestX);
int ceilZ = qMin(qMax((int)ceils.z, 0), highestZ);
float upperLeft = src[floorZ * _width + floorX] * heightScale;
float upperRight = src[floorZ * _width + ceilX] * heightScale;
float lowerLeft = src[ceilZ * _width + floorX] * heightScale;
float lowerRight = src[ceilZ * _width + ceilX] * heightScale;
// find the distance to the next x coordinate
float xDistance = FLT_MAX;
if (direction.x > 0.0f) {
xDistance = (ceils.x - entry.x) / direction.x;
} else if (direction.x < 0.0f) {
xDistance = (floors.x - entry.x) / direction.x;
}
// and the distance to the next z coordinate
float zDistance = FLT_MAX;
if (direction.z > 0.0f) {
zDistance = (ceils.z - entry.z) / direction.z;
} else if (direction.z < 0.0f) {
zDistance = (floors.z - entry.z) / direction.z;
}
// the exit distance is the lower of those two
float exitDistance = qMin(xDistance, zDistance);
glm::vec3 exit, nextFloors = floors, nextCeils = ceils;
if (exitDistance == FLT_MAX) {
withinBounds = false; // line points upwards/downwards; check this cell only
} else {
// find the exit point and the next cell, and determine whether it's still within the bounds
exit = entry + exitDistance * direction;
withinBounds = (exit.y >= 0.0f && exit.y <= highestY);
if (exitDistance == xDistance) {
if (direction.x > 0.0f) {
nextFloors.x += 1.0f;
withinBounds &= (nextCeils.x += 1.0f) <= highestX;
} else {
withinBounds &= (nextFloors.x -= 1.0f) >= 0.0f;
nextCeils.x -= 1.0f;
}
}
if (exitDistance == zDistance) {
if (direction.z > 0.0f) {
nextFloors.z += 1.0f;
withinBounds &= (nextCeils.z += 1.0f) <= highestZ;
} else {
withinBounds &= (nextFloors.z -= 1.0f) >= 0.0f;
nextCeils.z -= 1.0f;
}
}
// check the vertical range of the ray against the ranges of the cell heights
if (qMin(entry.y, exit.y) > qMax(qMax(upperLeft, upperRight), qMax(lowerLeft, lowerRight)) ||
qMax(entry.y, exit.y) < qMin(qMin(upperLeft, upperRight), qMin(lowerLeft, lowerRight))) {
entry = exit;
floors = nextFloors;
ceils = nextCeils;
accumulatedDistance += exitDistance;
continue;
}
}
// having passed the bounds check, we must check against the planes
glm::vec3 relativeEntry = entry - glm::vec3(floors.x, upperLeft, floors.z);
// first check the triangle including the Z+ segment
glm::vec3 lowerNormal(lowerLeft - lowerRight, 1.0f, upperLeft - lowerLeft);
float lowerProduct = glm::dot(lowerNormal, direction);
if (lowerProduct != 0.0f) {
float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct;
glm::vec3 intersection = relativeEntry + planeDistance * direction;
if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f &&
intersection.z >= intersection.x) {
distance = rayDistance + (accumulatedDistance + planeDistance) * _increment;
normal = glm::normalize(lowerNormal);
return true;
}
}
// then the one with the X+ segment
glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight);
float upperProduct = glm::dot(upperNormal, direction);
if (upperProduct != 0.0f) {
float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct;
glm::vec3 intersection = relativeEntry + planeDistance * direction;
if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f &&
intersection.x >= intersection.z) {
distance = rayDistance + (accumulatedDistance + planeDistance) * _increment;
normal = glm::normalize(upperNormal);
return true;
}
}
// no joy; continue on our way
entry = exit;
floors = nextFloors;
ceils = nextCeils;
accumulatedDistance += exitDistance;
}
return false;
}

View file

@ -644,9 +644,24 @@ public:
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
/// Checks whether this spanner has its own colors.
virtual bool hasOwnColors() const;
/// Checks whether this spanner has its own materials.
virtual bool hasOwnMaterials() const;
/// Checks whether the spanner contains the specified point.
virtual bool contains(const glm::vec3& point);
/// Retrieves the color at the specified point.
virtual QRgb getColor(const glm::vec3& point);
/// Retrieves the material at the specified point.
virtual int getMaterial(const glm::vec3& point);
/// Retrieves a reference to the list of materials.
virtual QVector<SharedObjectPointer>& getMaterials();
/// Finds the intersection, if any, between the specified line segment and the spanner.
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
@ -848,4 +863,37 @@ private:
QUrl _url;
};
/// A heightfield represented as a spanner.
class Heightfield : public Transformable {
Q_OBJECT
public:
Heightfield(const Box& bounds, float increment, const QByteArray& height, const QByteArray& color,
const QByteArray& material, const QVector<SharedObjectPointer>& materials);
QByteArray& getHeight() { return _height; }
QByteArray& getColor() { return _color; }
QByteArray& getMaterial() { return _material; }
virtual bool hasOwnColors() const;
virtual bool hasOwnMaterials() const;
virtual QRgb getColor(const glm::vec3& point);
virtual int getMaterial(const glm::vec3& point);
virtual QVector<SharedObjectPointer>& getMaterials();
virtual bool contains(const glm::vec3& point);
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
private:
float _increment;
int _width;
float _heightScale;
QByteArray _height;
QByteArray _color;
QByteArray _material;
QVector<SharedObjectPointer> _materials;
};
#endif // hifi_MetavoxelData_h

View file

@ -662,13 +662,36 @@ int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) {
if (info.size > _blockSize) {
return DEFAULT_ORDER;
}
QVector<QRgb> oldColorContents;
VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue<VoxelColorDataPointer>();
QVector<QRgb> oldColorContents = (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) ?
colorPointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME);
if (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) {
oldColorContents = colorPointer->getContents();
} else {
oldColorContents = QVector<QRgb>(VOXEL_BLOCK_VOLUME);
}
QVector<QRgb> hermiteContents;
VoxelHermiteDataPointer hermitePointer = info.inputValues.at(1).getInlineValue<VoxelHermiteDataPointer>();
if (hermitePointer && hermitePointer->getSize() == VOXEL_BLOCK_SAMPLES) {
hermiteContents = hermitePointer->getContents();
} else {
hermiteContents = QVector<QRgb>(VOXEL_BLOCK_VOLUME * VoxelHermiteData::EDGE_COUNT);
}
QByteArray materialContents;
QVector<SharedObjectPointer> materials;
VoxelMaterialDataPointer materialPointer = info.inputValues.at(2).getInlineValue<VoxelMaterialDataPointer>();
if (materialPointer && materialPointer->getSize() == VOXEL_BLOCK_SAMPLES) {
materialContents = materialPointer->getContents();
materials = materialPointer->getMaterials();
} else {
materialContents = QByteArray(VOXEL_BLOCK_VOLUME, 0);
}
float scale = VOXEL_BLOCK_SIZE / info.size;
QVector<QRgb> colorContents = oldColorContents;
Box overlap = info.getBounds().getIntersection(_spanner->getBounds());
float scale = VOXEL_BLOCK_SIZE / info.size;
overlap.minimum = (overlap.minimum - info.minimum) * scale;
overlap.maximum = (overlap.maximum - info.minimum) * scale;
int minX = glm::ceil(overlap.minimum.x);
@ -682,30 +705,41 @@ int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) {
bool flipped = (qAlpha(rgb) == 0);
float step = 1.0f / scale;
glm::vec3 position(0.0f, 0.0f, info.minimum.z + minZ * step);
for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) {
position.y = info.minimum.y + minY * step;
for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY;
destY += VOXEL_BLOCK_SAMPLES, position.y += step) {
position.x = info.minimum.x + minX * step;
for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) {
if (_spanner->contains(position)) {
*destX = rgb;
if (_spanner->hasOwnColors()) {
for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) {
position.y = info.minimum.y + minY * step;
for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY;
destY += VOXEL_BLOCK_SAMPLES, position.y += step) {
position.x = info.minimum.x + minX * step;
for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) {
if (_spanner->contains(position)) {
*destX = _spanner->getColor(position);
}
}
}
}
} else {
for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) {
position.y = info.minimum.y + minY * step;
for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY;
destY += VOXEL_BLOCK_SAMPLES, position.y += step) {
position.x = info.minimum.x + minX * step;
for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) {
if (_spanner->contains(position)) {
*destX = rgb;
}
}
}
}
}
VoxelColorDataPointer newColorPointer(new VoxelColorData(colorContents, VOXEL_BLOCK_SAMPLES));
info.outputValues[0] = AttributeValue(info.inputValues.at(0).getAttribute(),
encodeInline<VoxelColorDataPointer>(newColorPointer));
VoxelHermiteDataPointer hermitePointer = info.inputValues.at(1).getInlineValue<VoxelHermiteDataPointer>();
QVector<QRgb> hermiteContents = (hermitePointer && hermitePointer->getSize() == VOXEL_BLOCK_SAMPLES) ?
hermitePointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME * VoxelHermiteData::EDGE_COUNT);
int hermiteArea = VOXEL_BLOCK_AREA * VoxelHermiteData::EDGE_COUNT;
int hermiteSamples = VOXEL_BLOCK_SAMPLES * VoxelHermiteData::EDGE_COUNT;
int hermiteMinX = minX, hermiteMinY = minY, hermiteMinZ = minZ;
int hermiteSizeX = sizeX, hermiteSizeY = sizeY, hermiteSizeZ = sizeZ;
if (minX > 0) {
@ -806,39 +840,53 @@ int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) {
}
}
}
}
}
VoxelHermiteDataPointer newHermitePointer(new VoxelHermiteData(hermiteContents, VOXEL_BLOCK_SAMPLES));
info.outputValues[1] = AttributeValue(info.inputValues.at(1).getAttribute(),
encodeInline<VoxelHermiteDataPointer>(newHermitePointer));
VoxelMaterialDataPointer materialPointer = info.inputValues.at(2).getInlineValue<VoxelMaterialDataPointer>();
QByteArray materialContents;
QVector<SharedObjectPointer> materials;
if (materialPointer && materialPointer->getSize() == VOXEL_BLOCK_SAMPLES) {
materialContents = materialPointer->getContents();
materials = materialPointer->getMaterials();
if (_spanner->hasOwnMaterials()) {
QHash<int, int> materialMap;
position.z = info.minimum.z + minZ * step;
for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) {
position.y = info.minimum.y + minY * step;
for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY;
destY += VOXEL_BLOCK_SAMPLES, position.y += step) {
position.x = info.minimum.x + minX * step;
for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) {
if (_spanner->contains(position)) {
int material = _spanner->getMaterial(position);
if (material != 0) {
int& mapping = materialMap[material];
if (mapping == 0) {
mapping = getMaterialIndex(_spanner->getMaterials().at(material - 1), materials,
materialContents);
}
material = mapping;
}
*destX = material;
}
}
}
}
} else {
materialContents = QByteArray(VOXEL_BLOCK_VOLUME, 0);
}
uchar materialIndex = getMaterialIndex(_material, materials, materialContents);
position.z = info.minimum.z + minZ * step;
for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) {
position.y = info.minimum.y + minY * step;
for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY;
destY += VOXEL_BLOCK_SAMPLES, position.y += step) {
position.x = info.minimum.x + minX * step;
for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) {
if (_spanner->contains(position)) {
*destX = materialIndex;
}
uchar materialIndex = getMaterialIndex(_material, materials, materialContents);
position.z = info.minimum.z + minZ * step;
for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) {
position.y = info.minimum.y + minY * step;
for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY;
destY += VOXEL_BLOCK_SAMPLES, position.y += step) {
position.x = info.minimum.x + minX * step;
for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) {
if (_spanner->contains(position)) {
*destX = materialIndex;
}
}
}
}
}
clearUnusedMaterials(materials, materialContents);
VoxelMaterialDataPointer newMaterialPointer(new VoxelMaterialData(materialContents, VOXEL_BLOCK_SAMPLES, materials));
info.outputValues[2] = AttributeValue(_inputs.at(2), encodeInline<VoxelMaterialDataPointer>(newMaterialPointer));
@ -846,6 +894,244 @@ int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) {
return STOP_RECURSION;
}
class HeightfieldClearFetchVisitor : public MetavoxelVisitor {
public:
HeightfieldClearFetchVisitor(const Box& bounds, float granularity);
const SharedObjectPointer& getSpanner() const { return _spanner; }
virtual int visit(MetavoxelInfo& info);
private:
Box _bounds;
Box _expandedBounds;
SharedObjectPointer _spanner;
Box _spannerBounds;
int _heightfieldWidth;
int _heightfieldHeight;
};
HeightfieldClearFetchVisitor::HeightfieldClearFetchVisitor(const Box& bounds, float granularity) :
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldColorAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), QVector<AttributePointer>() <<
AttributeRegistry::getInstance()->getHeightfieldAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldColorAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute()) {
// find the bounds of all voxel nodes intersected
float nodeSize = VOXEL_BLOCK_SIZE * glm::pow(2.0f, glm::floor(glm::log(granularity) / glm::log(2.0f)));
_bounds.minimum = glm::floor(bounds.minimum / nodeSize) * nodeSize;
_bounds.maximum = glm::ceil(bounds.maximum / nodeSize) * nodeSize;
// expand to include edges
_expandedBounds = _bounds;
float increment = nodeSize / VOXEL_BLOCK_SIZE;
_expandedBounds.maximum.x += increment;
_expandedBounds.maximum.z += increment;
}
int HeightfieldClearFetchVisitor::visit(MetavoxelInfo& info) {
Box bounds = info.getBounds();
if (!bounds.intersects(_expandedBounds)) {
return STOP_RECURSION;
}
if (!info.isLeaf) {
return DEFAULT_ORDER;
}
HeightfieldHeightDataPointer heightPointer = info.inputValues.at(0).getInlineValue<HeightfieldHeightDataPointer>();
if (!heightPointer) {
return STOP_RECURSION;
}
QByteArray contents(heightPointer->getContents());
int size = glm::sqrt((float)contents.size());
float heightScale = size / info.size;
Box overlap = bounds.getIntersection(_expandedBounds);
int srcX = (overlap.minimum.x - info.minimum.x) * heightScale;
int srcY = (overlap.minimum.z - info.minimum.z) * heightScale;
int srcWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) * heightScale);
int srcHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) * heightScale);
char* src = contents.data() + srcY * size + srcX;
// check for non-zero values
bool foundNonZero = false;
for (int y = 0; y < srcHeight; y++, src += (size - srcWidth)) {
for (char* end = src + srcWidth; src != end; src++) {
if (*src != 0) {
foundNonZero = true;
goto outerBreak;
}
}
}
outerBreak:
// if everything is zero, we're done
if (!foundNonZero) {
return STOP_RECURSION;
}
// create spanner if necessary
Heightfield* spanner = static_cast<Heightfield*>(_spanner.data());
float increment = 1.0f / heightScale;
if (!spanner) {
_spannerBounds.minimum = glm::floor(_bounds.minimum / increment) * increment;
_spannerBounds.maximum = (glm::ceil(_bounds.maximum / increment) + glm::vec3(1.0f, 0.0f, 1.0f)) * increment;
_spannerBounds.minimum.y = bounds.minimum.y;
_spannerBounds.maximum.y = bounds.maximum.y;
_heightfieldWidth = (int)glm::round((_spannerBounds.maximum.x - _spannerBounds.minimum.x) / increment) + 1;
_heightfieldHeight = (int)glm::round((_spannerBounds.maximum.z - _spannerBounds.minimum.z) / increment) + 1;
int heightfieldArea = _heightfieldWidth * _heightfieldHeight;
_spanner = spanner = new Heightfield(_spannerBounds, increment, QByteArray(heightfieldArea, 0),
QByteArray(heightfieldArea * DataBlock::COLOR_BYTES, 0), QByteArray(heightfieldArea, 0),
QVector<SharedObjectPointer>());
}
// copy the inner area
overlap = bounds.getIntersection(_spannerBounds);
int destX = (overlap.minimum.x - _spannerBounds.minimum.x) * heightScale;
int destY = (overlap.minimum.z - _spannerBounds.minimum.z) * heightScale;
int destWidth = (int)glm::round((overlap.maximum.x - overlap.minimum.x) * heightScale);
int destHeight = (int)glm::round((overlap.maximum.z - overlap.minimum.z) * heightScale);
char* dest = spanner->getHeight().data() + destY * _heightfieldWidth + destX;
srcX = (overlap.minimum.x - info.minimum.x) * heightScale;
srcY = (overlap.minimum.z - info.minimum.z) * heightScale;
src = contents.data() + srcY * size + srcX;
for (int y = 0; y < destHeight; y++, dest += _heightfieldWidth, src += size) {
memcpy(dest, src, destWidth);
}
// clear the inner area
Box innerBounds = _spannerBounds;
innerBounds.minimum.x += increment;
innerBounds.minimum.z += increment;
innerBounds.maximum.x -= increment;
innerBounds.maximum.z -= increment;
Box innerOverlap = bounds.getIntersection(innerBounds);
destX = (innerOverlap.minimum.x - info.minimum.x) * heightScale;
destY = (innerOverlap.minimum.z - info.minimum.z) * heightScale;
destWidth = glm::ceil((innerOverlap.maximum.x - innerOverlap.minimum.x) * heightScale);
destHeight = glm::ceil((innerOverlap.maximum.z - innerOverlap.minimum.z) * heightScale);
dest = contents.data() + destY * size + destX;
for (int y = 0; y < destHeight; y++, dest += size) {
memset(dest, 0, destWidth);
}
// see if there are any non-zero values left
foundNonZero = false;
dest = contents.data();
for (char* end = dest + contents.size(); dest != end; dest++) {
if (*dest != 0) {
foundNonZero = true;
break;
}
}
// if all is gone, clear the node
if (!foundNonZero) {
info.outputValues[0] = AttributeValue(_outputs.at(0),
encodeInline<HeightfieldHeightDataPointer>(HeightfieldHeightDataPointer()));
info.outputValues[1] = AttributeValue(_outputs.at(1),
encodeInline<HeightfieldColorDataPointer>(HeightfieldColorDataPointer()));
info.outputValues[2] = AttributeValue(_outputs.at(2),
encodeInline<HeightfieldMaterialDataPointer>(HeightfieldMaterialDataPointer()));
return STOP_RECURSION;
}
HeightfieldHeightDataPointer newHeightPointer(new HeightfieldHeightData(contents));
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldHeightDataPointer>(newHeightPointer));
HeightfieldColorDataPointer colorPointer = info.inputValues.at(1).getInlineValue<HeightfieldColorDataPointer>();
if (colorPointer) {
contents = colorPointer->getContents();
size = glm::sqrt((float)contents.size() / DataBlock::COLOR_BYTES);
heightScale = size / info.size;
// copy the inner area
destX = (overlap.minimum.x - _spannerBounds.minimum.x) * heightScale;
destY = (overlap.minimum.z - _spannerBounds.minimum.z) * heightScale;
destWidth = (int)glm::round((overlap.maximum.x - overlap.minimum.x) * heightScale);
destHeight = (int)glm::round((overlap.maximum.z - overlap.minimum.z) * heightScale);
dest = spanner->getColor().data() + (destY * _heightfieldWidth + destX) * DataBlock::COLOR_BYTES;
srcX = (overlap.minimum.x - info.minimum.x) * heightScale;
srcY = (overlap.minimum.z - info.minimum.z) * heightScale;
src = contents.data() + (srcY * size + srcX) * DataBlock::COLOR_BYTES;
for (int y = 0; y < destHeight; y++, dest += _heightfieldWidth * DataBlock::COLOR_BYTES,
src += size * DataBlock::COLOR_BYTES) {
memcpy(dest, src, destWidth * DataBlock::COLOR_BYTES);
}
destX = (innerOverlap.minimum.x - info.minimum.x) * heightScale;
destY = (innerOverlap.minimum.z - info.minimum.z) * heightScale;
destWidth = glm::ceil((innerOverlap.maximum.x - innerOverlap.minimum.x) * heightScale);
destHeight = glm::ceil((innerOverlap.maximum.z - innerOverlap.minimum.z) * heightScale);
dest = contents.data() + (destY * size + destX) * DataBlock::COLOR_BYTES;
for (int y = 0; y < destHeight; y++, dest += size * DataBlock::COLOR_BYTES) {
memset(dest, 0, destWidth * DataBlock::COLOR_BYTES);
}
HeightfieldColorDataPointer newColorPointer(new HeightfieldColorData(contents));
info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline<HeightfieldColorDataPointer>(newColorPointer));
}
HeightfieldMaterialDataPointer materialPointer = info.inputValues.at(2).getInlineValue<HeightfieldMaterialDataPointer>();
if (materialPointer) {
contents = materialPointer->getContents();
QVector<SharedObjectPointer> materials = materialPointer->getMaterials();
size = glm::sqrt((float)contents.size());
heightScale = size / info.size;
// copy the inner area
destX = (overlap.minimum.x - _spannerBounds.minimum.x) * heightScale;
destY = (overlap.minimum.z - _spannerBounds.minimum.z) * heightScale;
destWidth = (int)glm::round((overlap.maximum.x - overlap.minimum.x) * heightScale);
destHeight = (int)glm::round((overlap.maximum.z - overlap.minimum.z) * heightScale);
uchar* dest = (uchar*)spanner->getMaterial().data() + destY * _heightfieldWidth + destX;
srcX = (overlap.minimum.x - info.minimum.x) * heightScale;
srcY = (overlap.minimum.z - info.minimum.z) * heightScale;
uchar* src = (uchar*)contents.data() + srcY * size + srcX;
QHash<int, int> materialMap;
for (int y = 0; y < destHeight; y++, dest += _heightfieldWidth, src += size) {
for (uchar* lineSrc = src, *lineDest = dest, *end = src + destWidth; lineSrc != end; lineSrc++, lineDest++) {
int material = *lineSrc;
if (material != 0) {
int& mapping = materialMap[material];
if (mapping == 0) {
mapping = getMaterialIndex(materials.at(material - 1), spanner->getMaterials(),
spanner->getMaterial());
}
material = mapping;
}
*lineDest = material;
}
}
destX = (innerOverlap.minimum.x - info.minimum.x) * heightScale;
destY = (innerOverlap.minimum.z - info.minimum.z) * heightScale;
destWidth = glm::ceil((innerOverlap.maximum.x - innerOverlap.minimum.x) * heightScale);
destHeight = glm::ceil((innerOverlap.maximum.z - innerOverlap.minimum.z) * heightScale);
dest = (uchar*)contents.data() + destY * size + destX;
for (int y = 0; y < destHeight; y++, dest += size) {
memset(dest, 0, destWidth);
}
clearUnusedMaterials(materials, contents);
HeightfieldMaterialDataPointer newMaterialPointer(new HeightfieldMaterialData(contents, materials));
info.outputValues[2] = AttributeValue(_outputs.at(2),
encodeInline<HeightfieldMaterialDataPointer>(newMaterialPointer));
}
return STOP_RECURSION;
}
void VoxelMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
// expand to fit the entire edit
Spanner* spanner = static_cast<Spanner*>(this->spanner.data());
@ -855,6 +1141,18 @@ void VoxelMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObject
// make sure it's either 100% transparent or 100% opaque
QColor color = averageColor;
color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f);
// clear/fetch any heightfield data
HeightfieldClearFetchVisitor heightfieldVisitor(spanner->getBounds(), spanner->getVoxelizationGranularity());
data.guide(heightfieldVisitor);
// voxelize the fetched heightfield, if any
if (heightfieldVisitor.getSpanner()) {
VoxelMaterialSpannerEditVisitor visitor(static_cast<Spanner*>(heightfieldVisitor.getSpanner().data()),
material, color);
data.guide(visitor);
}
VoxelMaterialSpannerEditVisitor visitor(spanner, material, color);
data.guide(visitor);
}