More work on heightfields.

This commit is contained in:
Andrzej Kapolka 2014-07-29 14:27:51 -07:00
parent aeee369286
commit d04eb6261e
4 changed files with 308 additions and 33 deletions

View file

@ -254,15 +254,22 @@ void PointBuffer::render() {
_buffer.release();
}
HeightfieldBuffer::HeightfieldBuffer(const QByteArray& height, const QByteArray& color, const QByteArray& normal) :
HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale,
const QByteArray& height, const QByteArray& color) :
_translation(translation),
_scale(scale),
_height(height),
_color(color),
_normal(normal),
_heightTexture(QOpenGLTexture::Target2D),
_colorTexture(QOpenGLTexture::Target2D),
_normalTexture(QOpenGLTexture::Target2D) {
_colorTexture(QOpenGLTexture::Target2D) {
}
class HeightfieldPoint {
public:
glm::vec2 textureCoord;
glm::vec3 vertex;
};
void HeightfieldBuffer::render() {
// initialize textures, etc. on first render
if (!_heightTexture.isCreated()) {
@ -273,21 +280,116 @@ void HeightfieldBuffer::render() {
_heightTexture.setData(QOpenGLTexture::Luminance, QOpenGLTexture::UInt8, _height.data());
_height.clear();
int colorSize = glm::sqrt(_color.size() / 3);
_colorTexture.setSize(colorSize, colorSize);
if (!_color.isEmpty()) {
int colorSize = glm::sqrt(_color.size() / 3);
_colorTexture.setSize(colorSize, colorSize);
}
_colorTexture.setFormat(QOpenGLTexture::RGBFormat);
_colorTexture.allocateStorage();
_colorTexture.setData(QOpenGLTexture::BGR, QOpenGLTexture::UInt8, _color.data());
_color.clear();
if (!_color.isEmpty()) {
_colorTexture.setData(QOpenGLTexture::BGR, QOpenGLTexture::UInt8, _color.data());
_color.clear();
} else {
const quint8 WHITE_COLOR[] = { 255, 255, 255 };
_colorTexture.setData(QOpenGLTexture::BGR, QOpenGLTexture::UInt8, const_cast<quint8*>(WHITE_COLOR));
}
}
// create the buffer objects lazily
int size = _heightTexture.width();
int sizeWithSkirt = size + 2;
int vertexCount = sizeWithSkirt * sizeWithSkirt;
int rows = sizeWithSkirt - 1;
int indexCount = rows * rows * 4;
BufferPair& bufferPair = _bufferPairs[size];
if (!bufferPair.first.isCreated()) {
QVector<HeightfieldPoint> vertices(vertexCount);
HeightfieldPoint* point = vertices.data();
int normalSize = glm::sqrt(_normal.size() / 3);
_normalTexture.setSize(normalSize, normalSize);
_normalTexture.setFormat(QOpenGLTexture::RGBFormat);
_normalTexture.allocateStorage();
_normalTexture.setData(QOpenGLTexture::BGR, QOpenGLTexture::UInt8, _normal.data());
_normal.clear();
float step = 1.0f / (size - 1);
float z = -step;
for (int i = 0; i < sizeWithSkirt; i++, z += step) {
float x = -step;
const float SKIRT_LENGTH = 1.0f;
float baseY = (i == 0 || i == sizeWithSkirt - 1) ? -SKIRT_LENGTH : 0.0f;
for (int j = 0; j < sizeWithSkirt; j++, point++, x += step) {
point->vertex = glm::vec3(x, (j == 0 || j == sizeWithSkirt - 1) ? -SKIRT_LENGTH : baseY, z);
point->textureCoord = glm::vec2(x, z);
}
}
bufferPair.first.setUsagePattern(QOpenGLBuffer::StaticDraw);
bufferPair.first.create();
bufferPair.first.bind();
bufferPair.first.allocate(vertices.constData(), vertexCount * sizeof(HeightfieldPoint));
QVector<int> indices(indexCount);
int* index = indices.data();
for (int i = 0; i < rows; i++) {
int lineIndex = i * sizeWithSkirt;
int nextLineIndex = (i + 1) * sizeWithSkirt;
for (int j = 0; j < rows; j++) {
*index++ = lineIndex + j;
*index++ = lineIndex + j + 1;
*index++ = nextLineIndex + j;
*index++ = nextLineIndex + j + 1;
}
}
bufferPair.second = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
bufferPair.second.create();
bufferPair.second.bind();
bufferPair.second.allocate(indices.constData(), indexCount * sizeof(int));
} else {
bufferPair.first.bind();
bufferPair.second.bind();
}
HeightfieldPoint* point = 0;
glVertexPointer(3, GL_FLOAT, sizeof(HeightfieldPoint), &point->vertex);
glTexCoordPointer(2, GL_FLOAT, sizeof(HeightfieldPoint), &point->textureCoord);
glPushMatrix();
glTranslatef(_translation.x, _translation.y, _translation.z);
glScalef(_scale, _scale, _scale);
_heightTexture.bind(0);
_colorTexture.bind(1);
glDrawRangeElements(GL_QUADS, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0);
_colorTexture.release(1);
_heightTexture.release(0);
glPopMatrix();
bufferPair.first.release();
bufferPair.second.release();
}
QHash<int, HeightfieldBuffer::BufferPair> HeightfieldBuffer::_bufferPairs;
void HeightfieldPreview::render(const glm::vec3& translation, float scale) const {
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
DefaultMetavoxelRendererImplementation::getHeightfieldProgram().bind();
glPushMatrix();
glTranslatef(translation.x, translation.y, translation.z);
glScalef(scale, scale, scale);
foreach (const BufferDataPointer& buffer, _buffers) {
buffer->render();
}
glPopMatrix();
DefaultMetavoxelRendererImplementation::getHeightfieldProgram().release();
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
BufferDataAttribute::BufferDataAttribute(const QString& name) :
@ -368,11 +470,14 @@ int PointAugmentVisitor::visit(MetavoxelInfo& info) {
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
_points.append(point);
}
if (info.size >= _pointLeafSize) {
BufferPointVector swapPoints;
_points.swap(swapPoints);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(
new PointBuffer(swapPoints))));
if (info.size >= _pointLeafSize) {
PointBuffer* buffer = NULL;
if (!_points.isEmpty()) {
BufferPointVector swapPoints;
_points.swap(swapPoints);
buffer = new PointBuffer(swapPoints);
}
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
}
return STOP_RECURSION;
}
@ -381,10 +486,13 @@ bool PointAugmentVisitor::postVisit(MetavoxelInfo& info) {
if (info.size != _pointLeafSize) {
return false;
}
BufferPointVector swapPoints;
_points.swap(swapPoints);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(
new PointBuffer(swapPoints))));
PointBuffer* buffer = NULL;
if (!_points.isEmpty()) {
BufferPointVector swapPoints;
_points.swap(swapPoints);
buffer = new PointBuffer(swapPoints);
}
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
return true;
}
@ -403,7 +511,18 @@ HeightfieldAugmentVisitor::HeightfieldAugmentVisitor(const MetavoxelLOD& lod) :
}
int HeightfieldAugmentVisitor::visit(MetavoxelInfo& info) {
return STOP_RECURSION;
if (info.isLeaf) {
HeightfieldBuffer* buffer = NULL;
HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldDataPointer>();
if (height) {
HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldDataPointer>();
buffer = new HeightfieldBuffer(info.minimum, info.size, height->getContents(),
color ? color->getContents() : QByteArray());
}
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
return STOP_RECURSION;
}
return DEFAULT_ORDER;
}
void DefaultMetavoxelRendererImplementation::augment(MetavoxelData& data, const MetavoxelData& previous,
@ -419,11 +538,19 @@ void DefaultMetavoxelRendererImplementation::augment(MetavoxelData& data, const
data.setRoot(pointBufferAttribute, root);
root->incrementReferenceCount();
}
const AttributePointer& heightfieldBufferAttribute =
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute();
root = expandedPrevious.getRoot(heightfieldBufferAttribute);
if (root) {
data.setRoot(heightfieldBufferAttribute, root);
root->incrementReferenceCount();
}
PointAugmentVisitor visitor(lod);
data.guideToDifferent(expandedPrevious, visitor);
PointAugmentVisitor pointAugmentVisitor(lod);
data.guideToDifferent(expandedPrevious, pointAugmentVisitor);
HeightfieldAugmentVisitor heightfieldAugmentVisitor(lod);
data.guideToDifferent(expandedPrevious, heightfieldAugmentVisitor);
}
class SpannerSimulateVisitor : public SpannerVisitor {
@ -530,16 +657,24 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
_pointProgram.release();
_heightfieldProgram.bind();
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
BufferRenderVisitor heightfieldRenderVisitor(Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(),
lod);
data.guide(heightfieldRenderVisitor);
_heightfieldProgram.release();
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glEnable(GL_BLEND);
}

View file

@ -125,18 +125,34 @@ private:
class HeightfieldBuffer : public BufferData {
public:
HeightfieldBuffer(const QByteArray& height, const QByteArray& color, const QByteArray& normal);
HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color);
virtual void render();
private:
glm::vec3 _translation;
float _scale;
QByteArray _height;
QByteArray _color;
QByteArray _normal;
QOpenGLTexture _heightTexture;
QOpenGLTexture _colorTexture;
QOpenGLTexture _normalTexture;
typedef QPair<QOpenGLBuffer, QOpenGLBuffer> BufferPair;
static QHash<int, BufferPair> _bufferPairs;
};
/// Convenience class for rendering a preview of a heightfield.
class HeightfieldPreview {
public:
void setBuffers(const QVector<BufferDataPointer>& buffers) { _buffers = buffers; }
void render(const glm::vec3& translation, float scale) const;
private:
QVector<BufferDataPointer> _buffers;
};
/// A client-side attribute that stores renderable buffers.
@ -157,7 +173,9 @@ class DefaultMetavoxelRendererImplementation : public MetavoxelRendererImplement
public:
static void init();
static ProgramObject& getHeightfieldProgram() { return _heightfieldProgram; }
Q_INVOKABLE DefaultMetavoxelRendererImplementation();
virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod);

View file

@ -15,11 +15,12 @@
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFileDialog>
#include <QFormLayout>
#include <QGroupBox>
#include <QLineEdit>
#include <QListWidget>
#include <QMetaProperty>
#include <QMessageBox>
#include <QMetaProperty>
#include <QOpenGLFramebufferObject>
#include <QPushButton>
@ -113,6 +114,7 @@ MetavoxelEditor::MetavoxelEditor() :
addTool(new RemoveSpannerTool(this));
addTool(new ClearSpannersTool(this));
addTool(new SetSpannerTool(this));
addTool(new ImportHeightfieldTool(this));
updateAttributes();
@ -891,3 +893,92 @@ void SetSpannerTool::applyEdit(const AttributePointer& attribute, const SharedOb
QThreadPool::globalInstance()->start(new Voxelizer(size, cellBounds,
spannerData->getVoxelizationGranularity(), directionImages));
}
ImportHeightfieldTool::ImportHeightfieldTool(MetavoxelEditor* editor) :
MetavoxelTool(editor, "Import Heightfield", false) {
QWidget* widget = new QWidget();
QFormLayout* form = new QFormLayout();
widget->setLayout(form);
layout()->addWidget(widget);
form->addRow("Height:", _height = new QPushButton());
connect(_height, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectHeightFile);
form->addRow("Color:", _color = new QPushButton());
connect(_color, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectColorFile);
}
bool ImportHeightfieldTool::appliesTo(const AttributePointer& attribute) const {
return attribute->inherits("HeightfieldAttribute");
}
void ImportHeightfieldTool::render() {
_preview.render(glm::vec3(), 1.0f);
}
void ImportHeightfieldTool::selectHeightFile() {
QString filename = QFileDialog::getOpenFileName(this, "Select Height Image", QString(), "Images (*.png *.jpg)");
if (filename.isNull()) {
return;
}
if (!_heightImage.load(filename)) {
QMessageBox::warning(this, "Invalid Image", "The selected image could not be read.");
return;
}
_heightImage = _heightImage.convertToFormat(QImage::Format_RGB888);
_height->setText(filename);
updatePreview();
}
void ImportHeightfieldTool::selectColorFile() {
QString filename = QFileDialog::getOpenFileName(this, "Select Color Image", QString(), "Images (*.png *.jpg)");
if (filename.isNull()) {
return;
}
if (!_colorImage.load(filename)) {
QMessageBox::warning(this, "Invalid Image", "The selected image could not be read.");
return;
}
_colorImage = _colorImage.convertToFormat(QImage::Format_RGB888);
_color->setText(filename);
updatePreview();
}
const int BLOCK_SIZE = 64;
void ImportHeightfieldTool::updatePreview() {
QVector<BufferDataPointer> buffers;
if (_heightImage.width() > 0 && _heightImage.height() > 0) {
float z = 0.0f;
for (int i = 0; i < _heightImage.height(); i += BLOCK_SIZE, z++) {
float x = 0.0f;
for (int j = 0; j < _heightImage.width(); j += BLOCK_SIZE, x++) {
QByteArray height(BLOCK_SIZE * BLOCK_SIZE, 0);
int rows = qMin(BLOCK_SIZE, _heightImage.height() - i);
int columns = qMin(BLOCK_SIZE, _heightImage.width() - j);
const int BYTES_PER_COLOR = 3;
for (int y = 0; y < rows; y++) {
uchar* src = _heightImage.scanLine(i + y);
char* dest = height.data() + y * BLOCK_SIZE;
for (int x = 0; x < columns; x++) {
*dest++ = *src;
src += BYTES_PER_COLOR;
}
}
QByteArray color;
if (!_colorImage.isNull()) {
color = QByteArray(BLOCK_SIZE * BLOCK_SIZE * BYTES_PER_COLOR, 0);
rows = qMax(0, qMin(BLOCK_SIZE, _colorImage.height() - i));
columns = qMax(0, qMin(BLOCK_SIZE, _colorImage.width() - j));
for (int y = 0; y < rows; y++) {
memcpy(color.data() + y * BLOCK_SIZE * BYTES_PER_COLOR, _colorImage.scanLine(i + y),
columns * BYTES_PER_COLOR);
}
}
buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, height, color)));
}
}
}
_preview.setBuffers(buffers);
}

View file

@ -15,6 +15,7 @@
#include <QList>
#include <QWidget>
#include "MetavoxelSystem.h"
#include "renderer/ProgramObject.h"
class QComboBox;
@ -223,4 +224,34 @@ protected:
virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner);
};
/// Allows importing a heightfield.
class ImportHeightfieldTool : public MetavoxelTool {
Q_OBJECT
public:
ImportHeightfieldTool(MetavoxelEditor* editor);
virtual bool appliesTo(const AttributePointer& attribute) const;
virtual void render();
private slots:
void selectHeightFile();
void selectColorFile();
private:
void updatePreview();
QPushButton* _height;
QPushButton* _color;
QImage _heightImage;
QImage _colorImage;
HeightfieldPreview _preview;
};
#endif // hifi_MetavoxelEditor_h