// // Spanner.cpp // libraries/metavoxels/src // // Created by Andrzej Kapolka on 11/10/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 // #include #include #include #include #include #include #include #include #include #include #include #include "MetavoxelData.h" #include "Spanner.h" using namespace std; REGISTER_META_OBJECT(Spanner) REGISTER_META_OBJECT(Heightfield) REGISTER_META_OBJECT(Sphere) REGISTER_META_OBJECT(Cuboid) REGISTER_META_OBJECT(StaticModel) REGISTER_META_OBJECT(MaterialObject) static int heightfieldHeightTypeId = registerSimpleMetaType(); static int heightfieldColorTypeId = registerSimpleMetaType(); static int heightfieldMaterialTypeId = registerSimpleMetaType(); static QItemEditorCreatorBase* createHeightfieldHeightEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } static QItemEditorCreatorBase* createHeightfieldColorEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } static QItemEditorCreatorBase* heightfieldHeightEditorCreator = createHeightfieldHeightEditorCreator(); static QItemEditorCreatorBase* heightfieldColorEditorCreator = createHeightfieldColorEditorCreator(); const float DEFAULT_PLACEMENT_GRANULARITY = 0.01f; const float DEFAULT_VOXELIZATION_GRANULARITY = powf(2.0f, -3.0f); namespace SettingHandles { const SettingHandle heightfieldDir("heightDir", QString()); } Spanner::Spanner() : _renderer(NULL), _placementGranularity(DEFAULT_PLACEMENT_GRANULARITY), _voxelizationGranularity(DEFAULT_VOXELIZATION_GRANULARITY), _merged(false), _willBeVoxelized(false) { } void Spanner::setBounds(const Box& bounds) { if (_bounds == bounds) { return; } emit boundsWillChange(); emit boundsChanged(_bounds = bounds); } bool Spanner::testAndSetVisited(int visit) { QMutexLocker locker(&_lastVisitsMutex); int& lastVisit = _lastVisits[QThread::currentThread()]; if (lastVisit == visit) { return false; } lastVisit = visit; return true; } SpannerRenderer* Spanner::getRenderer() { if (!_renderer) { QByteArray className = getRendererClassName(); const QMetaObject* metaObject = Bitstream::getMetaObject(className); if (!metaObject) { qDebug() << "Unknown class name:" << className; metaObject = &SpannerRenderer::staticMetaObject; } _renderer = static_cast(metaObject->newInstance()); connect(this, &QObject::destroyed, _renderer, &QObject::deleteLater); _renderer->init(this); } return _renderer; } bool Spanner::isHeightfield() const { return false; } float Spanner::getHeight(const glm::vec3& location) const { return -FLT_MAX; } bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { return _bounds.findRayIntersection(origin, direction, distance); } Spanner* Spanner::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase) { return this; } Spanner* Spanner::fillHeight(const glm::vec3& position, float radius) { return this; } Spanner* Spanner::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize) { return this; } bool Spanner::hasOwnColors() const { return false; } bool Spanner::hasOwnMaterials() const { return false; } QRgb Spanner::getColorAt(const glm::vec3& point) { return 0; } int Spanner::getMaterialAt(const glm::vec3& point) { return 0; } QVector& Spanner::getMaterials() { static QVector emptyMaterials; return emptyMaterials; } bool Spanner::contains(const glm::vec3& point) { return false; } bool Spanner::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { return false; } QByteArray Spanner::getRendererClassName() const { return "SpannerRendererer"; } QAtomicInt Spanner::_nextVisit(1); SpannerRenderer::SpannerRenderer() { } void SpannerRenderer::init(Spanner* spanner) { _spanner = spanner; } void SpannerRenderer::simulate(float deltaTime) { // nothing by default } void SpannerRenderer::render(const MetavoxelLOD& lod, bool contained, bool cursor) { // nothing by default } bool SpannerRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { return false; } Transformable::Transformable() : _scale(1.0f) { } void Transformable::setTranslation(const glm::vec3& translation) { if (_translation != translation) { emit translationChanged(_translation = translation); } } void Transformable::setRotation(const glm::quat& rotation) { if (_rotation != rotation) { emit rotationChanged(_rotation = rotation); } } void Transformable::setScale(float scale) { if (_scale != scale) { emit scaleChanged(_scale = scale); } } ColorTransformable::ColorTransformable() : _color(Qt::white) { } void ColorTransformable::setColor(const QColor& color) { if (_color != color) { emit colorChanged(_color = color); } } Sphere::Sphere() { connect(this, SIGNAL(translationChanged(const glm::vec3&)), SLOT(updateBounds())); connect(this, SIGNAL(scaleChanged(float)), SLOT(updateBounds())); updateBounds(); } bool Sphere::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { return findRaySphereIntersection(origin, direction, getTranslation(), getScale(), distance); } bool Sphere::contains(const glm::vec3& point) { return glm::distance(point, getTranslation()) <= getScale(); } bool Sphere::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { glm::vec3 relativeStart = start - getTranslation(); glm::vec3 vector = end - start; float a = glm::dot(vector, vector); if (a == 0.0f) { return false; } float b = glm::dot(relativeStart, vector); float radicand = b * b - a * (glm::dot(relativeStart, relativeStart) - getScale() * getScale()); if (radicand < 0.0f) { return false; } float radical = glm::sqrt(radicand); float first = (-b - radical) / a; if (first >= 0.0f && first <= 1.0f) { distance = first; normal = glm::normalize(relativeStart + vector * distance); return true; } float second = (-b + radical) / a; if (second >= 0.0f && second <= 1.0f) { distance = second; normal = glm::normalize(relativeStart + vector * distance); return true; } return false; } QByteArray Sphere::getRendererClassName() const { return "SphereRenderer"; } void Sphere::updateBounds() { glm::vec3 extent(getScale(), getScale(), getScale()); setBounds(Box(getTranslation() - extent, getTranslation() + extent)); } Cuboid::Cuboid() : _aspectY(1.0f), _aspectZ(1.0f) { connect(this, &Cuboid::translationChanged, this, &Cuboid::updateBoundsAndPlanes); connect(this, &Cuboid::rotationChanged, this, &Cuboid::updateBoundsAndPlanes); connect(this, &Cuboid::scaleChanged, this, &Cuboid::updateBoundsAndPlanes); connect(this, &Cuboid::aspectYChanged, this, &Cuboid::updateBoundsAndPlanes); connect(this, &Cuboid::aspectZChanged, this, &Cuboid::updateBoundsAndPlanes); updateBoundsAndPlanes(); } void Cuboid::setAspectY(float aspectY) { if (_aspectY != aspectY) { emit aspectYChanged(_aspectY = aspectY); } } void Cuboid::setAspectZ(float aspectZ) { if (_aspectZ != aspectZ) { emit aspectZChanged(_aspectZ = aspectZ); } } bool Cuboid::contains(const glm::vec3& point) { glm::vec4 point4(point, 1.0f); for (int i = 0; i < PLANE_COUNT; i++) { if (glm::dot(_planes[i], point4) > 0.0f) { return false; } } return true; } bool Cuboid::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { glm::vec4 start4(start, 1.0f); glm::vec4 vector = glm::vec4(end - start, 0.0f); for (int i = 0; i < PLANE_COUNT; i++) { // first check the segment against the plane float divisor = glm::dot(_planes[i], vector); if (glm::abs(divisor) < EPSILON) { continue; } float t = -glm::dot(_planes[i], start4) / divisor; if (t < 0.0f || t > 1.0f) { continue; } // now that we've established that it intersects the plane, check against the other sides glm::vec4 point = start4 + vector * t; const int PLANES_PER_AXIS = 2; int indexOffset = ((i / PLANES_PER_AXIS) + 1) * PLANES_PER_AXIS; for (int j = 0; j < PLANE_COUNT - PLANES_PER_AXIS; j++) { if (glm::dot(_planes[(indexOffset + j) % PLANE_COUNT], point) > 0.0f) { goto outerContinue; } } distance = t; normal = glm::vec3(_planes[i]); return true; outerContinue: ; } return false; } QByteArray Cuboid::getRendererClassName() const { return "CuboidRenderer"; } void Cuboid::updateBoundsAndPlanes() { glm::vec3 extent(getScale(), getScale() * _aspectY, getScale() * _aspectZ); glm::mat4 rotationMatrix = glm::mat4_cast(getRotation()); setBounds(glm::translate(getTranslation()) * rotationMatrix * Box(-extent, extent)); glm::vec4 translation4 = glm::vec4(getTranslation(), 1.0f); _planes[0] = glm::vec4(glm::vec3(rotationMatrix[0]), -glm::dot(rotationMatrix[0], translation4) - getScale()); _planes[1] = glm::vec4(glm::vec3(-rotationMatrix[0]), glm::dot(rotationMatrix[0], translation4) - getScale()); _planes[2] = glm::vec4(glm::vec3(rotationMatrix[1]), -glm::dot(rotationMatrix[1], translation4) - getScale() * _aspectY); _planes[3] = glm::vec4(glm::vec3(-rotationMatrix[1]), glm::dot(rotationMatrix[1], translation4) - getScale() * _aspectY); _planes[4] = glm::vec4(glm::vec3(rotationMatrix[2]), -glm::dot(rotationMatrix[2], translation4) - getScale() * _aspectZ); _planes[5] = glm::vec4(glm::vec3(-rotationMatrix[2]), glm::dot(rotationMatrix[2], translation4) - getScale() * _aspectZ); } StaticModel::StaticModel() { } void StaticModel::setURL(const QUrl& url) { if (_url != url) { emit urlChanged(_url = url); } } bool StaticModel::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { // delegate to renderer, if we have one return _renderer ? _renderer->findRayIntersection(origin, direction, distance) : Spanner::findRayIntersection(origin, direction, distance); } QByteArray StaticModel::getRendererClassName() const { return "StaticModelRenderer"; } DataBlock::~DataBlock() { } const int HeightfieldData::SHARED_EDGE = 1; HeightfieldData::HeightfieldData(int width) : _width(width) { } const int HEIGHTFIELD_DATA_HEADER_SIZE = sizeof(qint32) * 4; static QByteArray encodeHeightfieldHeight(int offsetX, int offsetY, int width, int height, const QVector& contents) { QByteArray inflated(HEIGHTFIELD_DATA_HEADER_SIZE, 0); qint32* header = (qint32*)inflated.data(); *header++ = offsetX; *header++ = offsetY; *header++ = width; *header++ = height; if (!contents.isEmpty()) { // encode with Paeth filter (see http://en.wikipedia.org/wiki/Portable_Network_Graphics#Filtering) QVector filteredContents(contents.size()); const quint16* src = contents.constData(); quint16* dest = filteredContents.data(); *dest++ = *src++; for (quint16* end = dest + width - 1; dest != end; dest++, src++) { *dest = *src - src[-1]; } for (int y = 1; y < height; y++) { *dest++ = *src - src[-width]; src++; for (quint16* end = dest + width - 1; dest != end; dest++, src++) { int a = src[-1]; int b = src[-width]; int c = src[-width - 1]; int p = a + b - c; int ad = abs(a - p); int bd = abs(b - p); int cd = abs(c - p); *dest = *src - (ad < bd ? (ad < cd ? a : c) : (bd < cd ? b : c)); } } inflated.append((const char*)filteredContents.constData(), filteredContents.size() * sizeof(quint16)); } return qCompress(inflated); } static QVector decodeHeightfieldHeight(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++; int payloadSize = inflated.size() - HEIGHTFIELD_DATA_HEADER_SIZE; QVector unfiltered(payloadSize / sizeof(quint16)); if (!unfiltered.isEmpty()) { quint16* dest = unfiltered.data(); const quint16* src = (const quint16*)(inflated.constData() + HEIGHTFIELD_DATA_HEADER_SIZE); *dest++ = *src++; for (quint16* end = dest + width - 1; dest != end; dest++, src++) { *dest = *src + dest[-1]; } for (int y = 1; y < height; y++) { *dest = (*src++) + dest[-width]; dest++; for (quint16* end = dest + width - 1; dest != end; dest++, src++) { int a = dest[-1]; int b = dest[-width]; int c = dest[-width - 1]; int p = a + b - c; int ad = abs(a - p); int bd = abs(b - p); int cd = abs(c - p); *dest = *src + (ad < bd ? (ad < cd ? a : c) : (bd < cd ? b : c)); } } } return unfiltered; } const int HeightfieldHeight::HEIGHT_BORDER = 1; const int HeightfieldHeight::HEIGHT_EXTENSION = SHARED_EDGE + 2 * HEIGHT_BORDER; HeightfieldHeight::HeightfieldHeight(int width, const QVector& contents) : HeightfieldData(width), _contents(contents) { } HeightfieldHeight::HeightfieldHeight(Bitstream& in, int bytes) { read(in, bytes); } HeightfieldHeight::HeightfieldHeight(Bitstream& in, int bytes, const HeightfieldHeightPointer& reference) { if (!reference) { read(in, bytes); return; } QMutexLocker locker(&reference->getEncodedDeltaMutex()); reference->setEncodedDelta(in.readAligned(bytes)); reference->setDeltaData(DataBlockPointer(this)); _width = reference->getWidth(); _contents = reference->getContents(); int offsetX, offsetY, width, height; QVector delta = decodeHeightfieldHeight(reference->getEncodedDelta(), offsetX, offsetY, width, height); if (delta.isEmpty()) { return; } if (offsetX == 0) { _contents = delta; _width = width; return; } int minX = offsetX - 1; int minY = offsetY - 1; const quint16* src = delta.constData(); quint16* dest = _contents.data() + minY * _width + minX; for (int y = 0; y < height; y++, src += width, dest += _width) { memcpy(dest, src, width * sizeof(quint16)); } } void HeightfieldHeight::write(Bitstream& out) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { _encoded = encodeHeightfieldHeight(0, 0, _width, _contents.size() / _width, _contents); } out << _encoded.size(); out.writeAligned(_encoded); } void HeightfieldHeight::writeDelta(Bitstream& out, const HeightfieldHeightPointer& reference) { if (!reference || reference->getWidth() != _width || reference->getContents().size() != _contents.size()) { write(out); return; } QMutexLocker locker(&reference->getEncodedDeltaMutex()); if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { int height = _contents.size() / _width; int minX = _width, minY = height; int maxX = -1, maxY = -1; const quint16* src = _contents.constData(); const quint16* ref = reference->getContents().constData(); for (int y = 0; y < height; y++) { bool difference = false; for (int x = 0; x < _width; x++) { if (*src++ != *ref++) { minX = qMin(minX, x); maxX = qMax(maxX, x); difference = true; } } if (difference) { minY = qMin(minY, y); maxY = qMax(maxY, y); } } QVector delta; int deltaWidth = 0, deltaHeight = 0; if (maxX >= minX) { deltaWidth = maxX - minX + 1; deltaHeight = maxY - minY + 1; delta = QVector(deltaWidth * deltaHeight); quint16* dest = delta.data(); src = _contents.constData() + minY * _width + minX; for (int y = 0; y < deltaHeight; y++, src += _width, dest += deltaWidth) { memcpy(dest, src, deltaWidth * sizeof(quint16)); } } reference->setEncodedDelta(encodeHeightfieldHeight(minX + 1, minY + 1, deltaWidth, deltaHeight, delta)); reference->setDeltaData(DataBlockPointer(this)); } out << reference->getEncodedDelta().size(); out.writeAligned(reference->getEncodedDelta()); } void HeightfieldHeight::read(Bitstream& in, int bytes) { int offsetX, offsetY, height; _contents = decodeHeightfieldHeight(_encoded = in.readAligned(bytes), offsetX, offsetY, _width, height); } Bitstream& operator<<(Bitstream& out, const HeightfieldHeightPointer& value) { if (value) { value->write(out); } else { out << 0; } return out; } Bitstream& operator>>(Bitstream& in, HeightfieldHeightPointer& value) { int size; in >> size; if (size == 0) { value = HeightfieldHeightPointer(); } else { value = new HeightfieldHeight(in, size); } return in; } template<> void Bitstream::writeRawDelta(const HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference) { if (value) { value->writeDelta(*this, reference); } else { *this << 0; } } template<> void Bitstream::readRawDelta(HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference) { int size; *this >> size; if (size == 0) { value = HeightfieldHeightPointer(); } else { value = new HeightfieldHeight(*this, size, reference); } } HeightfieldHeightEditor::HeightfieldHeightEditor(QWidget* parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(); setLayout(layout); layout->addWidget(_select = new QPushButton("Select")); connect(_select, &QPushButton::clicked, this, &HeightfieldHeightEditor::select); layout->addWidget(_clear = new QPushButton("Clear")); connect(_clear, &QPushButton::clicked, this, &HeightfieldHeightEditor::clear); _clear->setEnabled(false); } void HeightfieldHeightEditor::setHeight(const HeightfieldHeightPointer& height) { if ((_height = height)) { _clear->setEnabled(true); } else { _clear->setEnabled(false); } } static int getHeightfieldSize(int size) { return (int)glm::pow(2.0f, glm::round(glm::log((float)size - HeightfieldData::SHARED_EDGE) / glm::log(2.0f))) + HeightfieldData::SHARED_EDGE; } void HeightfieldHeightEditor::select() { QString result = QFileDialog::getOpenFileName(this, "Select Height Image", SettingHandles::heightfieldDir.get(), "Images (*.png *.jpg *.bmp *.raw *.mdr)"); if (result.isNull()) { return; } SettingHandles::heightfieldDir.set(QFileInfo(result).path()); const quint16 CONVERSION_OFFSET = 1; QString lowerResult = result.toLower(); bool isMDR = lowerResult.endsWith(".mdr"); if (lowerResult.endsWith(".raw") || isMDR) { QFile input(result); input.open(QIODevice::ReadOnly); QDataStream in(&input); in.setByteOrder(QDataStream::LittleEndian); if (isMDR) { const int MDR_HEADER_SIZE = 1024; input.seek(MDR_HEADER_SIZE); } int available = input.bytesAvailable() / sizeof(quint16); QVector rawContents(available); for (quint16* height = rawContents.data(), *end = height + available; height != end; height++) { in >> *height; } if (rawContents.isEmpty()) { QMessageBox::warning(this, "Invalid Image", "The selected image could not be read."); return; } int rawSize = glm::sqrt((float)rawContents.size()); int size = getHeightfieldSize(rawSize) + 2 * HeightfieldHeight::HEIGHT_BORDER; QVector contents(size * size); quint16* dest = contents.data() + (size + 1) * HeightfieldHeight::HEIGHT_BORDER; const quint16* src = rawContents.constData(); const float CONVERSION_SCALE = 65534.0f / numeric_limits::max(); for (int i = 0; i < rawSize; i++, dest += size) { for (quint16* lineDest = dest, *end = dest + rawSize; lineDest != end; lineDest++, src++) { *lineDest = (quint16)(*src * CONVERSION_SCALE) + CONVERSION_OFFSET; } } emit heightChanged(_height = new HeightfieldHeight(size, contents)); _clear->setEnabled(true); return; } QImage image; if (!image.load(result)) { QMessageBox::warning(this, "Invalid Image", "The selected image could not be read."); return; } image = image.convertToFormat(QImage::Format_ARGB32); int width = getHeightfieldSize(image.width()) + 2 * HeightfieldHeight::HEIGHT_BORDER; int height = getHeightfieldSize(image.height()) + 2 * HeightfieldHeight::HEIGHT_BORDER; QVector contents(width * height); quint16* dest = contents.data() + (width + 1) * HeightfieldHeight::HEIGHT_BORDER; const float CONVERSION_SCALE = 65534.0f / numeric_limits::max(); for (int i = 0; i < image.height(); i++, dest += width) { const QRgb* src = (const QRgb*)image.constScanLine(i); for (quint16* lineDest = dest, *end = dest + image.width(); lineDest != end; lineDest++, src++) { *lineDest = (qAlpha(*src) < numeric_limits::max()) ? 0 : (quint16)(qRed(*src) * CONVERSION_SCALE) + CONVERSION_OFFSET; } } emit heightChanged(_height = new HeightfieldHeight(width, contents)); _clear->setEnabled(true); } void HeightfieldHeightEditor::clear() { emit heightChanged(_height = HeightfieldHeightPointer()); _clear->setEnabled(false); } static QByteArray encodeHeightfieldColor(int offsetX, int offsetY, int width, int height, const QByteArray& contents) { QByteArray inflated(HEIGHTFIELD_DATA_HEADER_SIZE, 0); qint32* header = (qint32*)inflated.data(); *header++ = offsetX; *header++ = offsetY; *header++ = width; *header++ = height; if (!contents.isEmpty()) { QBuffer buffer(&inflated); buffer.open(QIODevice::WriteOnly | QIODevice::Append); QImage((const uchar*)contents.constData(), width, height, width * DataBlock::COLOR_BYTES, QImage::Format_RGB888).save(&buffer, "JPG"); } return qCompress(inflated); } static QByteArray decodeHeightfieldColor(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++; int payloadSize = inflated.size() - HEIGHTFIELD_DATA_HEADER_SIZE; if (payloadSize == 0) { return QByteArray(); } QImage image = QImage::fromData((const uchar*)inflated.constData() + HEIGHTFIELD_DATA_HEADER_SIZE, payloadSize, "JPG"); if (image.format() != QImage::Format_RGB888) { image = image.convertToFormat(QImage::Format_RGB888); } QByteArray contents(width * height * DataBlock::COLOR_BYTES, 0); char* dest = contents.data(); int stride = width * DataBlock::COLOR_BYTES; for (int y = 0; y < height; y++, dest += stride) { memcpy(dest, image.constScanLine(y), stride); } return contents; } HeightfieldColor::HeightfieldColor(int width, const QByteArray& contents) : HeightfieldData(width), _contents(contents) { } HeightfieldColor::HeightfieldColor(Bitstream& in, int bytes) { read(in, bytes); } HeightfieldColor::HeightfieldColor(Bitstream& in, int bytes, const HeightfieldColorPointer& reference) { if (!reference) { read(in, bytes); return; } QMutexLocker locker(&reference->getEncodedDeltaMutex()); reference->setEncodedDelta(in.readAligned(bytes)); reference->setDeltaData(DataBlockPointer(this)); _width = reference->getWidth(); _contents = reference->getContents(); int offsetX, offsetY, width, height; QByteArray delta = decodeHeightfieldColor(reference->getEncodedDelta(), offsetX, offsetY, width, height); if (delta.isEmpty()) { return; } if (offsetX == 0) { _contents = delta; _width = width; return; } int minX = offsetX - 1; int minY = offsetY - 1; const char* src = delta.constData(); char* dest = _contents.data() + (minY * _width + minX) * DataBlock::COLOR_BYTES; for (int y = 0; y < height; y++, src += width * DataBlock::COLOR_BYTES, dest += _width * DataBlock::COLOR_BYTES) { memcpy(dest, src, width * DataBlock::COLOR_BYTES); } } void HeightfieldColor::write(Bitstream& out) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { _encoded = encodeHeightfieldColor(0, 0, _width, _contents.size() / (_width * DataBlock::COLOR_BYTES), _contents); } out << _encoded.size(); out.writeAligned(_encoded); } void HeightfieldColor::writeDelta(Bitstream& out, const HeightfieldColorPointer& reference) { if (!reference || reference->getWidth() != _width || reference->getContents().size() != _contents.size()) { write(out); return; } QMutexLocker locker(&reference->getEncodedDeltaMutex()); if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { int height = _contents.size() / (_width * DataBlock::COLOR_BYTES); int minX = _width, minY = height; int maxX = -1, maxY = -1; const char* src = _contents.constData(); const char* ref = reference->getContents().constData(); for (int y = 0; y < height; y++) { bool difference = false; for (int x = 0; x < _width; x++, src += DataBlock::COLOR_BYTES, ref += DataBlock::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); } } QByteArray delta; int deltaWidth = 0, deltaHeight = 0; if (maxX >= minX) { deltaWidth = maxX - minX + 1; deltaHeight = maxY - minY + 1; delta = QByteArray(deltaWidth * deltaHeight * DataBlock::COLOR_BYTES, 0); char* dest = delta.data(); src = _contents.constData() + (minY * _width + minX) * DataBlock::COLOR_BYTES; for (int y = 0; y < deltaHeight; y++, src += _width * DataBlock::COLOR_BYTES, dest += deltaWidth * DataBlock::COLOR_BYTES) { memcpy(dest, src, deltaWidth * DataBlock::COLOR_BYTES); } } reference->setEncodedDelta(encodeHeightfieldColor(minX + 1, minY + 1, deltaWidth, deltaHeight, delta)); reference->setDeltaData(DataBlockPointer(this)); } out << reference->getEncodedDelta().size(); out.writeAligned(reference->getEncodedDelta()); } void HeightfieldColor::read(Bitstream& in, int bytes) { int offsetX, offsetY, height; _contents = decodeHeightfieldColor(_encoded = in.readAligned(bytes), offsetX, offsetY, _width, height); } Bitstream& operator<<(Bitstream& out, const HeightfieldColorPointer& value) { if (value) { value->write(out); } else { out << 0; } return out; } Bitstream& operator>>(Bitstream& in, HeightfieldColorPointer& value) { int size; in >> size; if (size == 0) { value = HeightfieldColorPointer(); } else { value = new HeightfieldColor(in, size); } return in; } template<> void Bitstream::writeRawDelta(const HeightfieldColorPointer& value, const HeightfieldColorPointer& reference) { if (value) { value->writeDelta(*this, reference); } else { *this << 0; } } template<> void Bitstream::readRawDelta(HeightfieldColorPointer& value, const HeightfieldColorPointer& reference) { int size; *this >> size; if (size == 0) { value = HeightfieldColorPointer(); } else { value = new HeightfieldColor(*this, size, reference); } } HeightfieldColorEditor::HeightfieldColorEditor(QWidget* parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(); setLayout(layout); layout->addWidget(_select = new QPushButton("Select")); connect(_select, &QPushButton::clicked, this, &HeightfieldColorEditor::select); layout->addWidget(_clear = new QPushButton("Clear")); connect(_clear, &QPushButton::clicked, this, &HeightfieldColorEditor::clear); _clear->setEnabled(false); } void HeightfieldColorEditor::setColor(const HeightfieldColorPointer& color) { if ((_color = color)) { _clear->setEnabled(true); } else { _clear->setEnabled(false); } } void HeightfieldColorEditor::select() { QString result = QFileDialog::getOpenFileName(this, "Select Color Image", SettingHandles::heightfieldDir.get(), "Images (*.png *.jpg *.bmp)"); if (result.isNull()) { return; } SettingHandles::heightfieldDir.get(QFileInfo(result).path()); QImage image; if (!image.load(result)) { QMessageBox::warning(this, "Invalid Image", "The selected image could not be read."); return; } image = image.convertToFormat(QImage::Format_RGB888); int width = getHeightfieldSize(image.width()); int height = getHeightfieldSize(image.height()); QByteArray contents(width * height * DataBlock::COLOR_BYTES, 0); char* dest = contents.data(); for (int i = 0; i < image.height(); i++, dest += width * DataBlock::COLOR_BYTES) { memcpy(dest, image.constScanLine(i), image.width() * DataBlock::COLOR_BYTES); } emit colorChanged(_color = new HeightfieldColor(width, contents)); _clear->setEnabled(true); } void HeightfieldColorEditor::clear() { emit colorChanged(_color = HeightfieldColorPointer()); _clear->setEnabled(false); } static QByteArray encodeHeightfieldMaterial(int offsetX, int offsetY, int width, int height, const QByteArray& contents) { QByteArray inflated(HEIGHTFIELD_DATA_HEADER_SIZE, 0); qint32* header = (qint32*)inflated.data(); *header++ = offsetX; *header++ = offsetY; *header++ = width; *header++ = height; inflated.append(contents); return qCompress(inflated); } static QByteArray decodeHeightfieldMaterial(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(HEIGHTFIELD_DATA_HEADER_SIZE); } HeightfieldMaterial::HeightfieldMaterial(int width, const QByteArray& contents, const QVector& materials) : HeightfieldData(width), _contents(contents), _materials(materials) { } HeightfieldMaterial::HeightfieldMaterial(Bitstream& in, int bytes) { read(in, bytes); } HeightfieldMaterial::HeightfieldMaterial(Bitstream& in, int bytes, const HeightfieldMaterialPointer& reference) { if (!reference) { read(in, bytes); return; } QMutexLocker locker(&reference->getEncodedDeltaMutex()); reference->setEncodedDelta(in.readAligned(bytes)); in.readDelta(_materials, reference->getMaterials()); reference->setDeltaData(DataBlockPointer(this)); _width = reference->getWidth(); _contents = reference->getContents(); int offsetX, offsetY, width, height; QByteArray delta = decodeHeightfieldMaterial(reference->getEncodedDelta(), offsetX, offsetY, width, height); if (delta.isEmpty()) { return; } if (offsetX == 0) { _contents = delta; _width = width; return; } int minX = offsetX - 1; int minY = offsetY - 1; const char* src = delta.constData(); char* dest = _contents.data() + minY * _width + minX; for (int y = 0; y < height; y++, src += width, dest += _width) { memcpy(dest, src, width); } } void HeightfieldMaterial::write(Bitstream& out) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { _encoded = encodeHeightfieldMaterial(0, 0, _width, _contents.size() / _width, _contents); } out << _encoded.size(); out.writeAligned(_encoded); out << _materials; } void HeightfieldMaterial::writeDelta(Bitstream& out, const HeightfieldMaterialPointer& reference) { if (!reference) { write(out); return; } QMutexLocker locker(&reference->getEncodedDeltaMutex()); if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { if (reference->getWidth() != _width || reference->getContents().size() != _contents.size()) { reference->setEncodedDelta(encodeHeightfieldMaterial(0, 0, _width, _contents.size() / _width, _contents)); } else { int height = _contents.size() / _width; int minX = _width, minY = height; int maxX = -1, maxY = -1; const char* src = _contents.constData(); const char* ref = reference->getContents().constData(); for (int y = 0; y < height; y++) { bool difference = false; for (int x = 0; x < _width; 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 deltaWidth = 0, deltaHeight = 0; if (maxX >= minX) { deltaWidth = maxX - minX + 1; deltaHeight = maxY - minY + 1; delta = QByteArray(deltaWidth * deltaHeight, 0); char* dest = delta.data(); src = _contents.constData() + minY * _width + minX; for (int y = 0; y < deltaHeight; y++, src += _width, dest += deltaWidth) { memcpy(dest, src, deltaWidth); } } reference->setEncodedDelta(encodeHeightfieldMaterial(minX + 1, minY + 1, deltaWidth, deltaHeight, delta)); } reference->setDeltaData(DataBlockPointer(this)); } out << reference->getEncodedDelta().size(); out.writeAligned(reference->getEncodedDelta()); out.writeDelta(_materials, reference->getMaterials()); } void HeightfieldMaterial::read(Bitstream& in, int bytes) { int offsetX, offsetY, height; _contents = decodeHeightfieldMaterial(_encoded = in.readAligned(bytes), offsetX, offsetY, _width, height); in >> _materials; } Bitstream& operator<<(Bitstream& out, const HeightfieldMaterialPointer& value) { if (value) { value->write(out); } else { out << 0; } return out; } Bitstream& operator>>(Bitstream& in, HeightfieldMaterialPointer& value) { int size; in >> size; if (size == 0) { value = HeightfieldMaterialPointer(); } else { value = new HeightfieldMaterial(in, size); } return in; } template<> void Bitstream::writeRawDelta(const HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference) { if (value) { value->writeDelta(*this, reference); } else { *this << 0; } } template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference) { int size; *this >> size; if (size == 0) { value = HeightfieldMaterialPointer(); } else { value = new HeightfieldMaterial(*this, size, reference); } } MaterialObject::MaterialObject() : _scaleS(1.0f), _scaleT(1.0f) { } int getMaterialIndex(const SharedObjectPointer& material, QVector& materials) { if (!(material && static_cast(material.data())->getDiffuse().isValid())) { return 0; } // first look for a matching existing material, noting the first reusable slot int firstEmptyIndex = -1; for (int i = 0; i < materials.size(); i++) { const SharedObjectPointer& existingMaterial = materials.at(i); if (existingMaterial) { if (existingMaterial->equals(material.data())) { return i + 1; } } else if (firstEmptyIndex == -1) { firstEmptyIndex = i; } } // if nothing found, use the first empty slot or append if (firstEmptyIndex != -1) { materials[firstEmptyIndex] = material; return firstEmptyIndex + 1; } if (materials.size() < numeric_limits::max()) { materials.append(material); return materials.size(); } return -1; } static QHash countIndices(const QByteArray& contents) { QHash counts; for (const uchar* src = (const uchar*)contents.constData(), *end = src + contents.size(); src != end; src++) { if (*src != 0) { counts[*src]++; } } return counts; } static uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, QByteArray& contents) { int index = getMaterialIndex(material, materials); if (index != -1) { return index; } // last resort: find the least-used material and remove it QHash counts = countIndices(contents); uchar materialIndex = 0; int lowestCount = INT_MAX; for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { if (it.value() < lowestCount) { materialIndex = it.key(); lowestCount = it.value(); } } contents.replace((char)materialIndex, (char)0); return materialIndex; } static void clearUnusedMaterials(QVector& materials, const QByteArray& contents) { QHash counts = countIndices(contents); for (int i = 0; i < materials.size(); i++) { if (counts.value(i + 1) == 0) { materials[i] = SharedObjectPointer(); } } while (!(materials.isEmpty() || materials.last())) { materials.removeLast(); } } static QHash countIndices(const QVector& contents) { QHash counts; foreach (const StackArray& array, contents) { if (array.isEmpty()) { continue; } for (const StackArray::Entry* entry = array.getEntryData(), *end = entry + array.getEntryCount(); entry != end; entry++) { if (entry->material != 0) { counts[entry->material]++; } } } return counts; } static uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, QVector& contents) { int index = getMaterialIndex(material, materials); if (index != -1) { return index; } // last resort: find the least-used material and remove it QHash counts = countIndices(contents); uchar materialIndex = 0; int lowestCount = INT_MAX; for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { if (it.value() < lowestCount) { materialIndex = it.key(); lowestCount = it.value(); } } for (StackArray* array = contents.data(), *end = array + contents.size(); array != end; array++) { if (array->isEmpty()) { continue; } for (StackArray::Entry* entry = array->getEntryData(), *end = entry + array->getEntryCount(); entry != end; entry++) { if (entry->material == materialIndex) { entry->material = 0; } } } return materialIndex; } static void clearUnusedMaterials(QVector& materials, const QVector& contents) { QHash counts = countIndices(contents); for (int i = 0; i < materials.size(); i++) { if (counts.value(i + 1) == 0) { materials[i] = SharedObjectPointer(); } } while (!(materials.isEmpty() || materials.last())) { materials.removeLast(); } } static QByteArray encodeHeightfieldStack(int offsetX, int offsetY, int width, int height, const QVector& contents) { QByteArray inflated(HEIGHTFIELD_DATA_HEADER_SIZE, 0); qint32* header = (qint32*)inflated.data(); *header++ = offsetX; *header++ = offsetY; *header++ = width; *header++ = height; foreach (const StackArray& stack, contents) { quint16 entries = stack.getEntryCount(); inflated.append((const char*)&entries, sizeof(quint16)); inflated.append(stack); } return qCompress(inflated); } static QVector decodeHeightfieldStack(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++; const char* src = inflated.constData() + HEIGHTFIELD_DATA_HEADER_SIZE; QVector contents(width * height); for (StackArray* dest = contents.data(), *end = dest + contents.size(); dest != end; dest++) { int entries = *(const quint16*)src; src += sizeof(quint16); if (entries > 0) { int bytes = StackArray::getSize(entries); *dest = StackArray(src, bytes); src += bytes; } } return contents; } StackArray::Entry::Entry() : color(0), material(0), hermiteX(0), hermiteY(0), hermiteZ(0) { } bool StackArray::Entry::isZero() const { return color == 0 && material == 0 && hermiteX == 0 && hermiteY == 0 && hermiteZ == 0; } bool StackArray::Entry::isMergeable(const Entry& other) const { return color == other.color && material == other.material && hermiteX == 0 && hermiteY == 0 && hermiteZ == 0; } static inline void setHermite(quint32& value, const glm::vec3& normal, float position) { value = qRgba(normal.x * numeric_limits::max(), normal.y * numeric_limits::max(), normal.z * numeric_limits::max(), position * numeric_limits::max()); } static inline float getHermite(QRgb value, glm::vec3& normal) { normal.x = (char)qRed(value) / (float)numeric_limits::max(); normal.y = (char)qGreen(value) / (float)numeric_limits::max(); normal.z = (char)qBlue(value) / (float)numeric_limits::max(); float length = glm::length(normal); if (length > 0.0f) { normal /= length; } return qAlpha(value) / (float)numeric_limits::max(); } void StackArray::Entry::setHermiteX(const glm::vec3& normal, float position) { setHermite(hermiteX, normal, position); } float StackArray::Entry::getHermiteX(glm::vec3& normal) const { return getHermite(hermiteX, normal); } void StackArray::Entry::setHermiteY(const glm::vec3& normal, float position) { setHermite(hermiteY, normal, position); } float StackArray::Entry::getHermiteY(glm::vec3& normal) const { return getHermite(hermiteY, normal); } void StackArray::Entry::setHermiteZ(const glm::vec3& normal, float position) { setHermite(hermiteZ, normal, position); } float StackArray::Entry::getHermiteZ(glm::vec3& normal) const { return getHermite(hermiteZ, normal); } int StackArray::getEntryAlpha(int y, float heightfieldHeight) const { int count = getEntryCount(); if (count != 0) { int relative = y - getPosition(); if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f || y < heightfieldHeight)) { return qAlpha(getEntryData()[qMax(relative, 0)].color); } } return (heightfieldHeight != 0.0f && y <= heightfieldHeight) ? numeric_limits::max() : 0; } StackArray::Entry& StackArray::getEntry(int y, float heightfieldHeight) { static Entry emptyEntry; int count = getEntryCount(); if (count != 0) { int relative = y - getPosition(); if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f || y < heightfieldHeight)) { return getEntryData()[qMax(relative, 0)]; } } return emptyEntry; } const StackArray::Entry& StackArray::getEntry(int y, float heightfieldHeight) const { static Entry emptyEntry; int count = getEntryCount(); if (count != 0) { int relative = y - getPosition(); if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f || y < heightfieldHeight)) { return getEntryData()[qMax(relative, 0)]; } } return emptyEntry; } void StackArray::getExtents(int& minimumY, int& maximumY) const { int count = getEntryCount(); if (count > 0) { int position = getPosition(); minimumY = qMin(minimumY, position); maximumY = qMax(maximumY, position + count - 1); } } bool StackArray::hasSetEntries() const { int count = getEntryCount(); if (count > 0) { for (const Entry* entry = getEntryData(), *end = entry + count; entry != end; entry++) { if (entry->isSet()) { return true; } } } return false; } HeightfieldStack::HeightfieldStack(int width, const QVector& contents, const QVector& materials) : HeightfieldData(width), _contents(contents), _materials(materials) { } HeightfieldStack::HeightfieldStack(Bitstream& in, int bytes) { read(in, bytes); } HeightfieldStack::HeightfieldStack(Bitstream& in, int bytes, const HeightfieldStackPointer& reference) { if (!reference) { read(in, bytes); return; } QMutexLocker locker(&reference->getEncodedDeltaMutex()); reference->setEncodedDelta(in.readAligned(bytes)); in.readDelta(_materials, reference->getMaterials()); reference->setDeltaData(DataBlockPointer(this)); _width = reference->getWidth(); _contents = reference->getContents(); int offsetX, offsetY, width, height; QVector delta = decodeHeightfieldStack(reference->getEncodedDelta(), offsetX, offsetY, width, height); if (delta.isEmpty()) { return; } if (offsetX == 0) { _contents = delta; _width = width; return; } int minX = offsetX - 1; int minY = offsetY - 1; const StackArray* src = delta.constData(); StackArray* dest = _contents.data() + minY * _width + minX; for (int y = 0; y < height; y++, src += width, dest += _width) { const StackArray* lineSrc = src; for (StackArray* lineDest = dest, *end = dest + width; lineDest != end; lineDest++, lineSrc++) { *lineDest = *lineSrc; } } } void HeightfieldStack::write(Bitstream& out) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { _encoded = encodeHeightfieldStack(0, 0, _width, _contents.size() / _width, _contents); } out << _encoded.size(); out.writeAligned(_encoded); out << _materials; } void HeightfieldStack::writeDelta(Bitstream& out, const HeightfieldStackPointer& reference) { if (!reference) { write(out); return; } QMutexLocker locker(&reference->getEncodedDeltaMutex()); if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { if (reference->getWidth() != _width || reference->getContents().size() != _contents.size()) { reference->setEncodedDelta(encodeHeightfieldStack(0, 0, _width, _contents.size() / _width, _contents)); } else { int height = _contents.size() / _width; int minX = _width, minY = height; int maxX = -1, maxY = -1; const StackArray* src = _contents.constData(); const StackArray* ref = reference->getContents().constData(); for (int y = 0; y < height; y++) { bool difference = false; for (int x = 0; x < _width; x++) { if (*src++ != *ref++) { minX = qMin(minX, x); maxX = qMax(maxX, x); difference = true; } } if (difference) { minY = qMin(minY, y); maxY = qMax(maxY, y); } } QVector delta; int deltaWidth = 0, deltaHeight = 0; if (maxX >= minX) { deltaWidth = maxX - minX + 1; deltaHeight = maxY - minY + 1; delta = QVector(deltaWidth * deltaHeight); StackArray* dest = delta.data(); src = _contents.constData() + minY * _width + minX; for (int y = 0; y < deltaHeight; y++, src += _width, dest += deltaWidth) { const StackArray* lineSrc = src; for (StackArray* lineDest = dest, *end = dest + deltaWidth; lineDest != end; lineDest++, lineSrc++) { *lineDest = *lineSrc; } } } reference->setEncodedDelta(encodeHeightfieldStack(minX + 1, minY + 1, deltaWidth, deltaHeight, delta)); } reference->setDeltaData(DataBlockPointer(this)); } out << reference->getEncodedDelta().size(); out.writeAligned(reference->getEncodedDelta()); out.writeDelta(_materials, reference->getMaterials()); } void HeightfieldStack::read(Bitstream& in, int bytes) { int offsetX, offsetY, height; _contents = decodeHeightfieldStack(_encoded = in.readAligned(bytes), offsetX, offsetY, _width, height); in >> _materials; } Bitstream& operator<<(Bitstream& out, const HeightfieldStackPointer& value) { if (value) { value->write(out); } else { out << 0; } return out; } Bitstream& operator>>(Bitstream& in, HeightfieldStackPointer& value) { int size; in >> size; if (size == 0) { value = HeightfieldStackPointer(); } else { value = new HeightfieldStack(in, size); } return in; } template<> void Bitstream::writeRawDelta(const HeightfieldStackPointer& value, const HeightfieldStackPointer& reference) { if (value) { value->writeDelta(*this, reference); } else { *this << 0; } } template<> void Bitstream::readRawDelta(HeightfieldStackPointer& value, const HeightfieldStackPointer& reference) { int size; *this >> size; if (size == 0) { value = HeightfieldStackPointer(); } else { value = new HeightfieldStack(*this, size, reference); } } bool HeightfieldStreamState::shouldSubdivide() const { return base.lod.shouldSubdivide(minimum, size); } bool HeightfieldStreamState::shouldSubdivideReference() const { return base.referenceLOD.shouldSubdivide(minimum, size); } bool HeightfieldStreamState::becameSubdivided() const { return base.lod.becameSubdivided(minimum, size, base.referenceLOD); } bool HeightfieldStreamState::becameSubdividedOrCollapsed() const { return base.lod.becameSubdividedOrCollapsed(minimum, size, base.referenceLOD); } const int X_MAXIMUM_FLAG = 1; const int Y_MAXIMUM_FLAG = 2; static glm::vec2 getNextMinimum(const glm::vec2& minimum, float nextSize, int index) { return minimum + glm::vec2( (index & X_MAXIMUM_FLAG) ? nextSize : 0.0f, (index & Y_MAXIMUM_FLAG) ? nextSize : 0.0f); } void HeightfieldStreamState::setMinimum(const glm::vec2& lastMinimum, int index) { minimum = getNextMinimum(lastMinimum, size, index); } HeightfieldNode::HeightfieldNode(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, const HeightfieldMaterialPointer& material, const HeightfieldStackPointer& stack) : _height(height), _color(color), _material(material), _stack(stack), _renderer(NULL) { } HeightfieldNode::HeightfieldNode(const HeightfieldNode& other) : _height(other.getHeight()), _color(other.getColor()), _material(other.getMaterial()), _stack(other.getStack()), _renderer(NULL) { for (int i = 0; i < CHILD_COUNT; i++) { _children[i] = other.getChild(i); } } HeightfieldNode::~HeightfieldNode() { delete _renderer; } const int HEIGHT_LEAF_SIZE = 256 + HeightfieldHeight::HEIGHT_EXTENSION; void HeightfieldNode::setContents(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, const HeightfieldMaterialPointer& material, const HeightfieldStackPointer& stack) { clearChildren(); int heightWidth = height->getWidth(); if (heightWidth <= HEIGHT_LEAF_SIZE) { _height = height; _color = color; _material = material; return; } int heightHeight = height->getContents().size() / heightWidth; int innerChildHeightWidth = (heightWidth - HeightfieldHeight::HEIGHT_EXTENSION) / 2; int innerChildHeightHeight = (heightHeight - HeightfieldHeight::HEIGHT_EXTENSION) / 2; int childHeightWidth = innerChildHeightWidth + HeightfieldHeight::HEIGHT_EXTENSION; int childHeightHeight = innerChildHeightHeight + HeightfieldHeight::HEIGHT_EXTENSION; for (int i = 0; i < CHILD_COUNT; i++) { QVector childHeightContents(childHeightWidth * childHeightHeight); quint16* heightDest = childHeightContents.data(); bool maximumX = (i & X_MAXIMUM_FLAG), maximumY = (i & Y_MAXIMUM_FLAG); const quint16* heightSrc = height->getContents().constData() + (maximumY ? innerChildHeightHeight * heightWidth : 0) + (maximumX ? innerChildHeightWidth : 0); for (int z = 0; z < childHeightHeight; z++, heightDest += childHeightWidth, heightSrc += heightWidth) { memcpy(heightDest, heightSrc, childHeightWidth * sizeof(quint16)); } HeightfieldColorPointer childColor; if (color) { int colorWidth = color->getWidth(); int colorHeight = color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); int innerChildColorWidth = (colorWidth - HeightfieldData::SHARED_EDGE) / 2; int innerChildColorHeight = (colorHeight - HeightfieldData::SHARED_EDGE) / 2; int childColorWidth = innerChildColorWidth + HeightfieldData::SHARED_EDGE; int childColorHeight = innerChildColorHeight + HeightfieldData::SHARED_EDGE; QByteArray childColorContents(childColorWidth * childColorHeight * DataBlock::COLOR_BYTES, 0); char* dest = childColorContents.data(); const char* src = color->getContents().constData() + ((maximumY ? innerChildColorHeight * colorWidth : 0) + (maximumX ? innerChildColorWidth : 0)) * DataBlock::COLOR_BYTES; for (int z = 0; z < childColorHeight; z++, dest += childColorWidth * DataBlock::COLOR_BYTES, src += colorWidth * DataBlock::COLOR_BYTES) { memcpy(dest, src, childColorWidth * DataBlock::COLOR_BYTES); } childColor = new HeightfieldColor(childColorWidth, childColorContents); } HeightfieldMaterialPointer childMaterial; if (material) { int materialWidth = material->getWidth(); int materialHeight = material->getContents().size() / materialWidth; int innerChildMaterialWidth = (materialWidth - HeightfieldData::SHARED_EDGE) / 2; int innerChildMaterialHeight = (materialHeight - HeightfieldData::SHARED_EDGE) / 2; int childMaterialWidth = innerChildMaterialWidth + HeightfieldData::SHARED_EDGE; int childMaterialHeight = innerChildMaterialHeight + HeightfieldData::SHARED_EDGE; QByteArray childMaterialContents(childMaterialWidth * childMaterialHeight, 0); QVector childMaterials; uchar* dest = (uchar*)childMaterialContents.data(); const uchar* src = (const uchar*)material->getContents().data() + (maximumY ? innerChildMaterialHeight * materialWidth : 0) + (maximumX ? innerChildMaterialWidth : 0); QHash materialMap; for (int z = 0; z < childMaterialHeight; z++, dest += childMaterialWidth, src += materialWidth) { const uchar* lineSrc = src; for (uchar* lineDest = dest, *end = dest + childMaterialWidth; lineDest != end; lineDest++, lineSrc++) { int value = *lineSrc; if (value != 0) { int& mapping = materialMap[value]; if (mapping == 0) { childMaterials.append(material->getMaterials().at(value - 1)); mapping = childMaterials.size(); } value = mapping; } *lineDest = value; } } childMaterial = new HeightfieldMaterial(childMaterialWidth, childMaterialContents, childMaterials); } HeightfieldStackPointer childStack; if (stack) { } _children[i] = new HeightfieldNode(); _children[i]->setContents(HeightfieldHeightPointer(new HeightfieldHeight(childHeightWidth, childHeightContents)), childColor, childMaterial, childStack); } mergeChildren(); } bool HeightfieldNode::isLeaf() const { for (int i = 0; i < CHILD_COUNT; i++) { if (_children[i]) { return false; } } return true; } bool HeightfieldNode::findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& origin, const glm::vec3& direction, float& distance) const { glm::quat inverseRotation = glm::inverse(rotation); glm::vec3 inverseScale = 1.0f / scale; glm::vec3 transformedOrigin = inverseRotation * (origin - translation) * inverseScale; glm::vec3 transformedDirection = inverseRotation * direction * inverseScale; float boundsDistance; if (!Box(glm::vec3(), glm::vec3(1.0f, 1.0f, 1.0f)).findRayIntersection(transformedOrigin, transformedDirection, boundsDistance)) { return false; } if (!isLeaf()) { float closestDistance = FLT_MAX; for (int i = 0; i < CHILD_COUNT; i++) { glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); float childDistance; if (_children[i]->findRayIntersection(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, origin, direction, childDistance)) { closestDistance = qMin(closestDistance, childDistance); } } if (closestDistance == FLT_MAX) { return false; } distance = closestDistance; return true; } float shortestDistance = FLT_MAX; float heightfieldDistance; if (findHeightfieldRayIntersection(transformedOrigin, transformedDirection, boundsDistance, heightfieldDistance)) { shortestDistance = heightfieldDistance; } float rendererDistance; if (_renderer && _renderer->findRayIntersection(translation, rotation, scale, origin, direction, boundsDistance, rendererDistance)) { shortestDistance = qMin(shortestDistance, rendererDistance); } if (shortestDistance == FLT_MAX) { return false; } distance = shortestDistance; return true; } const float HERMITE_GRANULARITY = 1.0f / numeric_limits::max(); void HeightfieldNode::getRangeAfterHeightPaint(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& position, float radius, float height, float& minimum, float& maximum) const { if (!_height) { return; } int heightWidth = _height->getWidth(); int heightHeight = _height->getContents().size() / heightWidth; int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; int highestHeightX = heightWidth - 1; int highestHeightZ = heightHeight - 1; glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); glm::vec3 center = glm::inverse(rotation) * (position - translation) * inverseScale + glm::vec3(1.0f, 0.0f, 1.0f); glm::vec3 extents = radius * inverseScale; if (center.x + extents.x < 0.0f || center.z + extents.z < 0.0f || center.x - extents.x > highestHeightX || center.z - extents.z > highestHeightZ) { return; } if (!isLeaf()) { for (int i = 0; i < CHILD_COUNT; i++) { glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); _children[i]->getRangeAfterHeightPaint(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, position, radius, height, minimum, maximum); } return; } glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(), glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); glm::vec3 end = glm::clamp(glm::ceil(center + extents), glm::vec3(), glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); const quint16* lineDest = _height->getContents().constData() + (int)start.z * heightWidth + (int)start.x; float squaredRadius = extents.x * extents.x; float squaredRadiusReciprocal = 1.0f / squaredRadius; float multiplierZ = extents.x / extents.z; float relativeHeight = height * numeric_limits::max() / scale.y; for (float z = start.z; z <= end.z; z += 1.0f) { const quint16* dest = lineDest; for (float x = start.x; x <= end.x; x += 1.0f, dest++) { float dx = x - center.x, dz = (z - center.z) * multiplierZ; float distanceSquared = dx * dx + dz * dz; if (distanceSquared <= squaredRadius) { // height falls off towards edges int value = *dest; if (value != 0) { value += relativeHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; minimum = qMin(minimum, (float)value); maximum = qMax(maximum, (float)value); } } } lineDest += heightWidth; } // make sure we increment in multiples of the voxel size float voxelStep = scale.x / innerHeightWidth; float heightIncrement = (1.0f - minimum) * scale.y / numeric_limits::max(); float incrementSteps = heightIncrement / voxelStep; if (glm::abs(incrementSteps - glm::round(incrementSteps)) > HERMITE_GRANULARITY) { minimum = 1.0f - voxelStep * glm::ceil(incrementSteps) * numeric_limits::max() / scale.y; } } HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& position, float radius, float height, bool set, bool erase, float normalizeScale, float normalizeOffset) { if (!_height) { return this; } int heightWidth = _height->getWidth(); int heightHeight = _height->getContents().size() / heightWidth; int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; int highestHeightX = heightWidth - 1; int highestHeightZ = heightHeight - 1; glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); glm::vec3 center = glm::inverse(rotation) * (position - translation) * inverseScale + glm::vec3(1.0f, 0.0f, 1.0f); glm::vec3 extents = radius * inverseScale; bool intersects = (center.x + extents.x >= 0.0f && center.z + extents.z >= 0.0f && center.x - extents.x <= highestHeightX && center.z - extents.z <= highestHeightZ); if (!intersects && normalizeScale == 1.0f && normalizeOffset == 0.0f) { return this; } if (!isLeaf()) { HeightfieldNode* newNode = this; for (int i = 0; i < CHILD_COUNT; i++) { glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); HeightfieldNode* newChild = _children[i]->paintHeight(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, position, radius, height, set, erase, normalizeScale, normalizeOffset); if (_children[i] != newChild) { if (newNode == this) { newNode = new HeightfieldNode(*this); } newNode->setChild(i, HeightfieldNodePointer(newChild)); } } if (newNode != this) { newNode->mergeChildren(true, false); } return newNode; } QVector newHeightContents = _height->getContents(); int stackWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE; QVector newStackContents; QVector newStackMaterials; if (_stack) { stackWidth = _stack->getWidth(); newStackContents = _stack->getContents(); newStackMaterials = _stack->getMaterials(); } int innerStackWidth = stackWidth - HeightfieldData::SHARED_EDGE; // renormalize if necessary maybeRenormalize(scale, normalizeScale, normalizeOffset, innerStackWidth, newHeightContents, newStackContents); if (!intersects) { return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), _color, _material, HeightfieldStackPointer(newStackContents.isEmpty() ? NULL : new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); } // now apply the actual change glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(), glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); glm::vec3 end = glm::clamp(glm::ceil(center + extents), glm::vec3(), glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); quint16* lineDest = newHeightContents.data() + (int)start.z * heightWidth + (int)start.x; float squaredRadius = extents.x * extents.x; float squaredRadiusReciprocal = 1.0f / squaredRadius; float multiplierZ = extents.x / extents.z; float relativeHeight = height * numeric_limits::max() / scale.y; quint16 heightValue = erase ? 0 : relativeHeight; for (float z = start.z; z <= end.z; z += 1.0f) { quint16* dest = lineDest; for (float x = start.x; x <= end.x; x += 1.0f, dest++) { float dx = x - center.x, dz = (z - center.z) * multiplierZ; float distanceSquared = dx * dx + dz * dz; if (distanceSquared <= squaredRadius) { if (erase || set) { *dest = heightValue; } else { // height falls off towards edges int value = *dest; if (value != 0) { *dest = value + relativeHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; } } } } lineDest += heightWidth; } return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), _color, _material, HeightfieldStackPointer(newStackContents.isEmpty() ? NULL : new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); } HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& position, float radius) { if (!_height) { return this; } int heightWidth = _height->getWidth(); int heightHeight = _height->getContents().size() / heightWidth; int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; int highestHeightX = heightWidth - 1; int highestHeightZ = heightHeight - 1; glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); glm::vec3 center = glm::inverse(rotation) * (position - translation) * inverseScale + glm::vec3(1.0f, 0.0f, 1.0f); glm::vec3 extents = radius * inverseScale; if (center.x + extents.x < 0.0f || center.z + extents.z < 0.0f || center.x - extents.x > highestHeightX || center.z - extents.z > highestHeightZ) { return this; } if (!isLeaf()) { HeightfieldNode* newNode = this; for (int i = 0; i < CHILD_COUNT; i++) { glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); HeightfieldNode* newChild = _children[i]->fillHeight(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, position, radius); if (_children[i] != newChild) { if (newNode == this) { newNode = new HeightfieldNode(*this); } newNode->setChild(i, HeightfieldNodePointer(newChild)); } } if (newNode != this) { newNode->mergeChildren(true, false); } return newNode; } if (!_stack) { return this; } QVector newHeightContents = _height->getContents(); QVector newStackContents = _stack->getContents(); int stackWidth = _stack->getWidth(); int stackHeight = newStackContents.size() / stackWidth; QVector newStackMaterials = _stack->getMaterials(); int colorWidth, colorHeight; QByteArray newColorContents; if (_color) { colorWidth = _color->getWidth(); colorHeight = _color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); newColorContents = _color->getContents(); } else { colorWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE; colorHeight = innerHeightHeight + HeightfieldData::SHARED_EDGE; newColorContents = QByteArray(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF); } int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; int materialWidth, materialHeight; QByteArray newMaterialContents; QVector newMaterialMaterials; if (_material) { materialWidth = _material->getWidth(); materialHeight = _material->getContents().size() / materialWidth; newMaterialContents = _material->getContents(); newMaterialMaterials = _material->getMaterials(); } else { materialWidth = colorWidth; materialHeight = colorHeight; newMaterialContents = QByteArray(materialWidth * materialHeight, 0); } int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(), glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); glm::vec3 end = glm::clamp(glm::ceil(center + extents), glm::vec3(), glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); float voxelStep = scale.x / innerHeightWidth; float voxelScale = scale.y / (numeric_limits::max() * voxelStep); quint16* lineDest = newHeightContents.data() + (int)start.z * heightWidth + (int)start.x; float squaredRadius = extents.x * extents.x; float multiplierZ = extents.x / extents.z; float colorStepX = (float)innerColorWidth / innerHeightWidth; float colorStepZ = (float)innerColorHeight / innerHeightHeight; float materialStepX = (float)innerMaterialWidth / innerHeightWidth; float materialStepZ = (float)innerMaterialHeight / innerHeightHeight; QHash materialMap; for (float z = start.z; z <= end.z; z += 1.0f) { quint16* dest = lineDest; for (float x = start.x; x <= end.x; x += 1.0f, dest++) { float dx = x - center.x, dz = (z - center.z) * multiplierZ; float distanceSquared = dx * dx + dz * dz; if (distanceSquared <= squaredRadius && x >= 1.0f && z >= 1.0f && x <= stackWidth && z <= stackHeight) { int stackX = (int)x - 1, stackZ = (int)z - 1; StackArray* stackDest = newStackContents.data() + stackZ * stackWidth + stackX; if (stackDest->isEmpty()) { continue; } int y = stackDest->getPosition() + stackDest->getEntryCount() - 1; for (const StackArray::Entry* entry = stackDest->getEntryData() + stackDest->getEntryCount() - 1; entry >= stackDest->getEntryData(); entry--, y--) { if (!entry->isSet()) { continue; } glm::vec3 normal; int newHeight = qMax((int)((y + entry->getHermiteY(normal)) / voxelScale), 1); if (newHeight < *dest) { break; } *dest = newHeight; for (int colorZ = stackZ * colorStepZ; (int)(colorZ / colorStepZ) == stackZ; colorZ++) { for (int colorX = stackX * colorStepX; (int)(colorX / colorStepX) == stackX; colorX++) { uchar* colorDest = (uchar*)newColorContents.data() + (colorZ * colorWidth + colorX) * DataBlock::COLOR_BYTES; colorDest[0] = qRed(entry->color); colorDest[1] = qGreen(entry->color); colorDest[2] = qBlue(entry->color); } } for (int materialZ = stackZ * materialStepZ; (int)(materialZ / materialStepZ) == stackZ; materialZ++) { for (int materialX = stackX * materialStepX; (int)(materialX / materialStepX) == stackX; materialX++) { int material = entry->material; if (material != 0) { int& mapping = materialMap[material]; if (mapping == 0) { mapping = getMaterialIndex(newStackMaterials.at(material - 1), newMaterialMaterials, newMaterialContents); } material = mapping; } newMaterialContents[materialZ * materialWidth + materialX] = material; } } break; } stackDest->clear(); } } lineDest += heightWidth; } clearUnusedMaterials(newMaterialMaterials, newMaterialContents); clearUnusedMaterials(newStackMaterials, newStackContents); return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), HeightfieldColorPointer(new HeightfieldColor(colorWidth, newColorContents)), HeightfieldMaterialPointer(new HeightfieldMaterial(materialWidth, newMaterialContents, newMaterialMaterials)), HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); } void HeightfieldNode::getRangeAfterEdit(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const Box& editBounds, float& minimum, float& maximum) const { if (!_height) { return; } int heightWidth = _height->getWidth(); int heightHeight = _height->getContents().size() / heightWidth; int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; glm::mat4 baseInverseTransform = glm::mat4_cast(glm::inverse(rotation)) * glm::translate(-translation); glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); glm::mat4 inverseTransform = glm::translate(glm::vec3(1.0f, 0.0f, 1.0f)) * glm::scale(inverseScale) * baseInverseTransform; Box transformedBounds = inverseTransform * editBounds; if (transformedBounds.maximum.x < 0.0f || transformedBounds.maximum.z < 0.0f || transformedBounds.minimum.x > heightWidth - 1 || transformedBounds.minimum.z > heightHeight - 1) { return; } if (!isLeaf()) { for (int i = 0; i < CHILD_COUNT; i++) { glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); _children[i]->getRangeAfterEdit(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, editBounds, minimum, maximum); } return; } glm::vec3 start = glm::floor(transformedBounds.minimum); glm::vec3 end = glm::ceil(transformedBounds.maximum); minimum = qMin(minimum, start.y); maximum = qMax(maximum, end.y); // make sure we increment in multiples of the voxel size float voxelStep = scale.x / innerHeightWidth; float heightIncrement = (1.0f - minimum) * scale.y / numeric_limits::max(); float incrementSteps = heightIncrement / voxelStep; if (glm::abs(incrementSteps - glm::round(incrementSteps)) > HERMITE_GRANULARITY) { minimum = 1.0f - voxelStep * glm::ceil(incrementSteps) * numeric_limits::max() / scale.y; } } HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize, float normalizeScale, float normalizeOffset) { if (!_height) { return this; } int heightWidth = _height->getWidth(); int heightHeight = _height->getContents().size() / heightWidth; int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; glm::vec3 expansion(1.0f / innerHeightWidth, 0.0f, 1.0f / innerHeightHeight); Box bounds = glm::translate(translation) * glm::mat4_cast(rotation) * Box(-expansion, scale + expansion); bool intersects = bounds.intersects(spanner->getBounds()); if (!intersects && normalizeScale == 1.0f && normalizeOffset == 0.0f) { return this; } if (!isLeaf()) { HeightfieldNode* newNode = this; for (int i = 0; i < CHILD_COUNT; i++) { glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); HeightfieldNode* newChild = _children[i]->setMaterial(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, spanner, material, color, paint, voxelize, normalizeScale, normalizeOffset); if (_children[i] != newChild) { if (newNode == this) { newNode = new HeightfieldNode(*this); } newNode->setChild(i, HeightfieldNodePointer(newChild)); } } if (newNode != this) { newNode->mergeChildren(); } return newNode; } int highestHeightX = heightWidth - 1; int highestHeightZ = heightHeight - 1; QVector newHeightContents = _height->getContents(); int stackWidth, stackHeight; QVector newStackContents; QVector newStackMaterials; if (_stack) { stackWidth = _stack->getWidth(); stackHeight = _stack->getContents().size() / stackWidth; newStackContents = _stack->getContents(); newStackMaterials = _stack->getMaterials(); } else { stackWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE; stackHeight = innerHeightHeight + HeightfieldData::SHARED_EDGE; newStackContents = QVector(stackWidth * stackHeight); } int innerStackWidth = stackWidth - HeightfieldData::SHARED_EDGE; int innerStackHeight = stackHeight - HeightfieldData::SHARED_EDGE; // renormalize if necessary maybeRenormalize(scale, normalizeScale, normalizeOffset, innerStackWidth, newHeightContents, newStackContents); if (!intersects) { return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), _color, _material, HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); } QVector oldHeightContents = newHeightContents; QVector oldStackContents = newStackContents; int colorWidth, colorHeight; QByteArray newColorContents; if (_color) { colorWidth = _color->getWidth(); colorHeight = _color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); newColorContents = _color->getContents(); } else { colorWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE; colorHeight = innerHeightHeight + HeightfieldData::SHARED_EDGE; newColorContents = QByteArray(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF); } int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; int materialWidth, materialHeight; QByteArray newMaterialContents; QVector newMaterialMaterials; if (_material) { materialWidth = _material->getWidth(); materialHeight = _material->getContents().size() / materialWidth; newMaterialContents = _material->getContents(); newMaterialMaterials = _material->getMaterials(); } else { materialWidth = colorWidth; materialHeight = colorHeight; newMaterialContents = QByteArray(materialWidth * materialHeight, 0); } int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; glm::mat4 baseInverseTransform = glm::mat4_cast(glm::inverse(rotation)) * glm::translate(-translation); glm::vec3 inverseScale(innerHeightWidth / scale.x, numeric_limits::max() / scale.y, innerHeightHeight / scale.z); glm::mat4 inverseTransform = glm::translate(glm::vec3(1.0f, 0.0f, 1.0f)) * glm::scale(inverseScale) * baseInverseTransform; Box transformedBounds = inverseTransform * spanner->getBounds(); glm::mat4 transform = glm::inverse(inverseTransform); glm::vec3 start = glm::ceil(transformedBounds.maximum); glm::vec3 end = glm::floor(transformedBounds.minimum); float stepX = 1.0f, stepZ = 1.0f; if (paint) { stepX = (float)innerHeightWidth / qMax(innerHeightWidth, qMax(innerColorWidth, innerMaterialWidth)); stepZ = (float)innerHeightHeight / qMax(innerHeightHeight, qMax(innerColorHeight, innerMaterialHeight)); } else { start.x += 1.0f; start.z += 1.0f; end.x -= 1.0f; end.z -= 1.0f; } float startX = glm::clamp(start.x, 0.0f, (float)highestHeightX), endX = glm::clamp(end.x, 0.0f, (float)highestHeightX); float startZ = glm::clamp(start.z, 0.0f, (float)highestHeightZ), endZ = glm::clamp(end.z, 0.0f, (float)highestHeightZ); float voxelStep = scale.x / innerHeightWidth; float voxelScale = scale.y / (numeric_limits::max() * voxelStep); int newTop = start.y * voxelScale; int newBottom = end.y * voxelScale; glm::vec3 worldStart = glm::vec3(transform * glm::vec4(startX, paint ? 0.0f : newTop / voxelScale, startZ, 1.0f)); glm::vec3 worldStepX = glm::vec3(transform * glm::vec4(stepX, 0.0f, 0.0f, 0.0f)); glm::vec3 worldStepY = glm::vec3(transform * glm::vec4(0.0f, 1.0f / voxelScale, 0.0f, 0.0f)); glm::vec3 worldStepZ = glm::vec3(transform * glm::vec4(0.0f, 0.0f, stepZ, 0.0f)); QRgb rgba = color.rgba(); bool erase = (color.alpha() == 0); uchar materialMaterialIndex = getMaterialIndex(material, newMaterialMaterials, newMaterialContents); uchar stackMaterialIndex = getMaterialIndex(material, newStackMaterials, newStackContents); bool hasOwnColors = spanner->hasOwnColors(); bool hasOwnMaterials = spanner->hasOwnMaterials(); QHash materialMappings; for (float z = startZ; z >= endZ; z -= stepZ, worldStart -= worldStepZ) { quint16* heightDest = newHeightContents.data() + (int)z * heightWidth; glm::vec3 worldPos = worldStart; for (float x = startX; x >= endX; x -= stepX, worldPos -= worldStepX) { quint16* heightLineDest = heightDest + (int)x; float distance; glm::vec3 normal; float colorX = (x - HeightfieldHeight::HEIGHT_BORDER) * innerColorWidth / innerHeightWidth; float colorZ = (z - HeightfieldHeight::HEIGHT_BORDER) * innerColorHeight / innerHeightHeight; uchar* colorDest = (colorX >= 0.0f && colorX <= innerColorWidth && colorZ >= 0.0f && colorZ <= innerColorHeight) ? ((uchar*)newColorContents.data() + ((int)colorZ * colorWidth + (int)colorX) * DataBlock::COLOR_BYTES) : NULL; float materialX = (x - HeightfieldHeight::HEIGHT_BORDER) * innerMaterialWidth / innerHeightWidth; float materialZ = (z - HeightfieldHeight::HEIGHT_BORDER) * innerMaterialHeight / innerHeightHeight; char* materialDest = (materialX >= 0.0f && materialX <= innerMaterialWidth && materialZ >= 0.0f && materialZ <= innerMaterialHeight) ? (newMaterialContents.data() + (int)materialZ * materialWidth + (int)materialX) : NULL; if (paint && *heightLineDest != 0 && spanner->contains(worldPos + worldStepY * (*heightLineDest * voxelScale))) { if (colorDest) { colorDest[0] = qRed(rgba); colorDest[1] = qGreen(rgba); colorDest[2] = qBlue(rgba); } if (materialDest) { *materialDest = materialMaterialIndex; } } float stackX = (x - HeightfieldHeight::HEIGHT_BORDER) * innerStackWidth / innerHeightWidth; float stackZ = (z - HeightfieldHeight::HEIGHT_BORDER) * innerStackHeight / innerHeightHeight; if (stackX >= 0.0f && stackX <= innerStackWidth && stackZ >= 0.0f && stackZ <= innerStackHeight) { StackArray* stackDest = newStackContents.data() + (int)stackZ * stackWidth + (int)stackX; if (paint) { if (stackDest->isEmpty() || glm::fract(x) != 0.0f || glm::fract(z) != 0.0f) { continue; } glm::vec3 pos = worldPos + worldStepY * (float)stackDest->getPosition(); for (StackArray::Entry* entryDest = stackDest->getEntryData(), *end = entryDest + stackDest->getEntryCount(); entryDest != end; entryDest++, pos += worldStepY) { if (entryDest->isSet() && spanner->contains(pos)) { entryDest->color = rgba; entryDest->material = stackMaterialIndex; } } continue; } int prepend = 0, append = 0; if (!stackDest->isEmpty()) { int oldBottom = stackDest->getPosition(); int oldTop = oldBottom + stackDest->getEntryCount() - 1; prepend = qMax(0, oldBottom - newBottom); append = qMax(0, newTop - oldTop); if (prepend != 0 || append != 0) { StackArray newStack(prepend + stackDest->getEntryCount() + append); memcpy(newStack.getEntryData() + prepend, stackDest->getEntryData(), stackDest->getEntryCount() * sizeof(StackArray::Entry)); for (StackArray::Entry* entryDest = newStack.getEntryData(), *end = entryDest + prepend; entryDest != end; entryDest++) { entryDest->color = end->color; entryDest->material = end->material; } *stackDest = newStack; stackDest->setPosition(qMin(oldBottom, newBottom)); } } else { *stackDest = StackArray(newTop - newBottom + 1); stackDest->setPosition(newBottom); prepend = stackDest->getEntryCount(); } const quint16* oldHeightLineDest = oldHeightContents.constData() + (int)z * heightWidth + (int)x; if (*heightLineDest != 0) { float voxelHeight = *heightLineDest * voxelScale; float left = oldHeightLineDest[-1] * voxelScale; float right = oldHeightLineDest[1] * voxelScale; float down = oldHeightLineDest[-heightWidth] * voxelScale; float up = oldHeightLineDest[heightWidth] * voxelScale; float deltaX = (left == 0.0f || right == 0.0f) ? 0.0f : (left - right); float deltaZ = (up == 0.0f || down == 0.0f) ? 0.0f : (down - up); for (int i = 0, total = prepend + append; i < total; i++) { int offset = (i < prepend) ? i : stackDest->getEntryCount() - append + (i - prepend); int y = stackDest->getPosition() + offset; StackArray::Entry* entryDest = stackDest->getEntryData() + offset; if (y > voxelHeight) { if (y <= right) { entryDest->setHermiteX(glm::normalize(glm::vec3(voxelHeight - right, 1.0f, deltaZ * 0.5f)), (right == voxelHeight) ? 0.5f : (y - voxelHeight) / (right - voxelHeight)); } if (y <= up) { entryDest->setHermiteZ(glm::normalize(glm::vec3(deltaX * 0.5f, 1.0f, voxelHeight - up)), (up == voxelHeight) ? 0.5f : (y - voxelHeight) / (up - voxelHeight)); } } else { if (right != 0.0f && y > right) { entryDest->setHermiteX(glm::normalize(glm::vec3(voxelHeight - right, 1.0f, deltaZ * 0.5f)), (right == voxelHeight) ? 0.5f : (y - voxelHeight) / (right - voxelHeight)); } if (up != 0.0f && y > up) { entryDest->setHermiteZ(glm::normalize(glm::vec3(deltaX * 0.5f, 1.0f, voxelHeight - up)), (up == voxelHeight) ? 0.5f : (y - voxelHeight) / (up - voxelHeight)); } if (colorDest) { entryDest->color = qRgb(colorDest[0], colorDest[1], colorDest[2]); } if (materialDest) { int index = *materialDest; if (index != 0) { int& mapping = materialMappings[index]; if (mapping == 0) { mapping = getMaterialIndex(newMaterialMaterials.at(index - 1), newStackMaterials, newStackContents); } index = mapping; } entryDest->material = index; } if (y + 1 > voxelHeight) { *heightLineDest = 0; entryDest->setHermiteY(glm::normalize(glm::vec3(deltaX, 2.0f, deltaZ)), voxelHeight - y); } } } } StackArray::Entry* entryDest = stackDest->getEntryData() + (newTop - stackDest->getPosition()); glm::vec3 pos = worldPos; float voxelHeight = *heightLineDest * voxelScale; float nextVoxelHeightX = heightLineDest[1] * voxelScale; float nextVoxelHeightZ = heightLineDest[heightWidth] * voxelScale; float oldVoxelHeight = *oldHeightLineDest * voxelScale; float oldNextVoxelHeightX = oldHeightLineDest[1] * voxelScale; float oldNextVoxelHeightZ = oldHeightLineDest[heightWidth] * voxelScale; // skip the actual set if voxelizing for (int y = voxelize ? newBottom - 1 : newTop; y >= newBottom; y--, entryDest--, pos -= worldStepY) { int oldCurrentAlpha = stackDest->getEntryAlpha(y, oldVoxelHeight); if (spanner->contains(pos)) { if (hasOwnColors && !erase) { entryDest->color = spanner->getColorAt(pos); } else { entryDest->color = rgba; } if (hasOwnMaterials && !erase) { int index = spanner->getMaterialAt(pos); if (index != 0) { int& mapping = materialMappings[index]; if (mapping == 0) { mapping = getMaterialIndex(spanner->getMaterials().at(index - 1), newStackMaterials, newStackContents); } index = mapping; } entryDest->material = index; } else { entryDest->material = stackMaterialIndex; } } int currentAlpha = stackDest->getEntryAlpha(y, voxelHeight); bool flipped = (color.alpha() == currentAlpha); int nextStackX = (int)stackX + 1; if (nextStackX <= innerStackWidth) { int nextAlphaX = newStackContents.at((int)stackZ * stackWidth + nextStackX).getEntryAlpha( y, nextVoxelHeightX); if (nextAlphaX == currentAlpha) { entryDest->hermiteX = 0; } else { float oldDistance = flipped ? 0.0f : 1.0f; if (currentAlpha == oldCurrentAlpha && nextAlphaX == oldStackContents.at((int)stackZ * stackWidth + nextStackX).getEntryAlpha(y, oldNextVoxelHeightX) && entryDest->hermiteX != 0) { oldDistance = qAlpha(entryDest->hermiteX) / (float)numeric_limits::max(); } if (flipped ? (spanner->intersects(pos + worldStepX, pos, distance, normal) && (distance = 1.0f - distance) >= oldDistance) : (spanner->intersects(pos, pos + worldStepX, distance, normal) && distance <= oldDistance)) { entryDest->setHermiteX(erase ? -normal : normal, distance); } } } bool nextAlphaY = stackDest->getEntryAlpha(y + 1, voxelHeight); if (nextAlphaY == currentAlpha) { entryDest->hermiteY = 0; } else { float oldDistance = flipped ? 0.0f : 1.0f; if (currentAlpha == oldCurrentAlpha && nextAlphaY == oldStackContents.at((int)stackZ * stackWidth + (int)stackX).getEntryAlpha(y + 1, oldVoxelHeight) && entryDest->hermiteY != 0) { oldDistance = qAlpha(entryDest->hermiteY) / (float)numeric_limits::max(); } if (flipped ? (spanner->intersects(pos + worldStepY, pos, distance, normal) && (distance = 1.0f - distance) >= oldDistance) : (spanner->intersects(pos, pos + worldStepY, distance, normal) && distance <= oldDistance)) { entryDest->setHermiteY(erase ? -normal : normal, distance); } } int nextStackZ = (int)stackZ + 1; if (nextStackZ <= innerStackHeight) { bool nextAlphaZ = newStackContents.at(nextStackZ * stackWidth + (int)stackX).getEntryAlpha( y, nextVoxelHeightZ); if (nextAlphaZ == currentAlpha) { entryDest->hermiteZ = 0; } else { float oldDistance = flipped ? 0.0f : 1.0f; if (currentAlpha == oldCurrentAlpha && nextAlphaZ == oldStackContents.at(nextStackZ * stackWidth + (int)stackX).getEntryAlpha(y, oldNextVoxelHeightZ) && entryDest->hermiteZ != 0) { oldDistance = qAlpha(entryDest->hermiteZ) / (float)numeric_limits::max(); } if (flipped ? (spanner->intersects(pos + worldStepZ, pos, distance, normal) && (distance = 1.0f - distance) >= oldDistance) : (spanner->intersects(pos, pos + worldStepZ, distance, normal) && distance <= oldDistance)) { entryDest->setHermiteZ(erase ? -normal : normal, distance); } } } } // prune zero entries from end, repeated entries from beginning int endPruneCount = 0; for (int i = stackDest->getEntryCount() - 1; i >= 0 && stackDest->getEntryData()[i].isZero(); i--) { endPruneCount++; } if (endPruneCount == stackDest->getEntryCount()) { stackDest->clear(); } else { stackDest->removeEntries(stackDest->getEntryCount() - endPruneCount, endPruneCount); int beginningPruneCount = 0; for (int i = 0; i < stackDest->getEntryCount() - 1 && stackDest->getEntryData()[i].isMergeable( stackDest->getEntryData()[i + 1]); i++) { beginningPruneCount++; } stackDest->removeEntries(0, beginningPruneCount); stackDest->getPositionRef() += beginningPruneCount; } } } } clearUnusedMaterials(newMaterialMaterials, newMaterialContents); clearUnusedMaterials(newStackMaterials, newStackContents); return new HeightfieldNode(paint ? _height : HeightfieldHeightPointer( new HeightfieldHeight(heightWidth, newHeightContents)), HeightfieldColorPointer(new HeightfieldColor(colorWidth, newColorContents)), HeightfieldMaterialPointer(new HeightfieldMaterial(materialWidth, newMaterialContents, newMaterialMaterials)), HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); } void HeightfieldNode::read(HeightfieldStreamState& state) { clearChildren(); if (!state.shouldSubdivide()) { state.base.stream >> _height >> _color >> _material >> _stack; return; } bool leaf; state.base.stream >> leaf; if (leaf) { state.base.stream >> _height >> _color >> _material >> _stack; } else { HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); _children[i] = new HeightfieldNode(); _children[i]->read(nextState); } mergeChildren(); } } void HeightfieldNode::write(HeightfieldStreamState& state) const { if (!state.shouldSubdivide()) { state.base.stream << _height << _color << _material << _stack; return; } bool leaf = isLeaf(); state.base.stream << leaf; if (leaf) { state.base.stream << _height << _color << _material << _stack; } else { HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); _children[i]->write(nextState); } } } void HeightfieldNode::readDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) { clearChildren(); if (!state.shouldSubdivide()) { state.base.stream.readDelta(_height, reference->getHeight()); state.base.stream.readDelta(_color, reference->getColor()); state.base.stream.readDelta(_material, reference->getMaterial()); state.base.stream.readDelta(_stack, reference->getStack()); return; } bool leaf; state.base.stream >> leaf; if (leaf) { state.base.stream.readDelta(_height, reference->getHeight()); state.base.stream.readDelta(_color, reference->getColor()); state.base.stream.readDelta(_material, reference->getMaterial()); state.base.stream.readDelta(_stack, reference->getStack()); } else { HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; if (reference->isLeaf() || !state.shouldSubdivideReference()) { for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); _children[i] = new HeightfieldNode(); _children[i]->read(nextState); } } else { for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); bool changed; state.base.stream >> changed; if (changed) { _children[i] = new HeightfieldNode(); _children[i]->readDelta(reference->getChild(i), nextState); } else { if (nextState.becameSubdividedOrCollapsed()) { _children[i] = reference->getChild(i)->readSubdivision(nextState); } else { _children[i] = reference->getChild(i); } } } } mergeChildren(); } } void HeightfieldNode::writeDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) const { if (!state.shouldSubdivide()) { state.base.stream.writeDelta(_height, reference->getHeight()); state.base.stream.writeDelta(_color, reference->getColor()); state.base.stream.writeDelta(_material, reference->getMaterial()); state.base.stream.writeDelta(_stack, reference->getStack()); return; } bool leaf = isLeaf(); state.base.stream << leaf; if (leaf) { state.base.stream.writeDelta(_height, reference->getHeight()); state.base.stream.writeDelta(_color, reference->getColor()); state.base.stream.writeDelta(_material, reference->getMaterial()); state.base.stream.writeDelta(_stack, reference->getStack()); } else { HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; if (reference->isLeaf() || !state.shouldSubdivideReference()) { for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); _children[i]->write(nextState); } } else { for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); if (_children[i] == reference->getChild(i)) { state.base.stream << false; if (nextState.becameSubdivided()) { _children[i]->writeSubdivision(nextState); } } else { state.base.stream << true; _children[i]->writeDelta(reference->getChild(i), nextState); } } } } } HeightfieldNode* HeightfieldNode::readSubdivision(HeightfieldStreamState& state) { if (state.shouldSubdivide()) { if (!state.shouldSubdivideReference()) { bool leaf; state.base.stream >> leaf; if (leaf) { return isLeaf() ? this : new HeightfieldNode(_height, _color, _material, _stack); } else { HeightfieldNode* newNode = new HeightfieldNode(_height, _color, _material, _stack); HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); newNode->_children[i] = new HeightfieldNode(); newNode->_children[i]->readSubdivided(nextState, state, this); } return newNode; } } else if (!isLeaf()) { HeightfieldNode* node = this; HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); if (nextState.becameSubdividedOrCollapsed()) { HeightfieldNode* child = _children[i]->readSubdivision(nextState); if (_children[i] != child) { if (node == this) { node = new HeightfieldNode(*this); } node->_children[i] = child; } } } if (node != this) { node->mergeChildren(); } return node; } } else if (!isLeaf()) { return new HeightfieldNode(_height, _color, _material, _stack); } return this; } void HeightfieldNode::writeSubdivision(HeightfieldStreamState& state) const { if (!state.shouldSubdivide()) { return; } bool leaf = isLeaf(); if (!state.shouldSubdivideReference()) { state.base.stream << leaf; if (!leaf) { HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); _children[i]->writeSubdivided(nextState, state, this); } } } else if (!leaf) { HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); if (nextState.becameSubdivided()) { _children[i]->writeSubdivision(nextState); } } } } void HeightfieldNode::readSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, const HeightfieldNode* ancestor) { clearChildren(); if (!state.shouldSubdivide()) { // TODO: subdivision encoding state.base.stream >> _height >> _color >> _material >> _stack; return; } bool leaf; state.base.stream >> leaf; if (leaf) { state.base.stream >> _height >> _color >> _material >> _stack; } else { HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); _children[i] = new HeightfieldNode(); _children[i]->readSubdivided(nextState, ancestorState, ancestor); } mergeChildren(); } } void HeightfieldNode::writeSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, const HeightfieldNode* ancestor) const { if (!state.shouldSubdivide()) { // TODO: subdivision encoding state.base.stream << _height << _color << _material << _stack; return; } bool leaf = isLeaf(); state.base.stream << leaf; if (leaf) { state.base.stream << _height << _color << _material << _stack; } else { HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); _children[i]->writeSubdivided(nextState, ancestorState, ancestor); } } } void HeightfieldNode::clearChildren() { for (int i = 0; i < CHILD_COUNT; i++) { _children[i].reset(); } } void HeightfieldNode::mergeChildren(bool height, bool colorMaterial) { if (isLeaf()) { return; } int heightWidth = 0; int heightHeight = 0; int colorWidth = 0; int colorHeight = 0; int materialWidth = 0; int materialHeight = 0; int stackWidth = 0; int stackHeight = 0; for (int i = 0; i < CHILD_COUNT; i++) { HeightfieldHeightPointer childHeight = _children[i]->getHeight(); if (childHeight) { int childHeightWidth = childHeight->getWidth(); int childHeightHeight = childHeight->getContents().size() / childHeightWidth; heightWidth = qMax(heightWidth, childHeightWidth); heightHeight = qMax(heightHeight, childHeightHeight); } HeightfieldColorPointer childColor = _children[i]->getColor(); if (childColor) { int childColorWidth = childColor->getWidth(); int childColorHeight = childColor->getContents().size() / (childColorWidth * DataBlock::COLOR_BYTES); colorWidth = qMax(colorWidth, childColorWidth); colorHeight = qMax(colorHeight, childColorHeight); } HeightfieldMaterialPointer childMaterial = _children[i]->getMaterial(); if (childMaterial) { int childMaterialWidth = childMaterial->getWidth(); int childMaterialHeight = childMaterial->getContents().size() / childMaterialWidth; materialWidth = qMax(materialWidth, childMaterialWidth); materialHeight = qMax(materialHeight, childMaterialHeight); } HeightfieldStackPointer childStack = _children[i]->getStack(); if (childStack) { int childStackWidth = childStack->getWidth(); int childStackHeight = childStack->getContents().size() / childStackWidth; stackWidth = qMax(stackWidth, childStackWidth); stackHeight = qMax(stackHeight, childStackHeight); } } if (heightWidth > 0 && height) { QVector heightContents(heightWidth * heightHeight); for (int i = 0; i < CHILD_COUNT; i++) { HeightfieldHeightPointer childHeight = _children[i]->getHeight(); if (!childHeight) { continue; } int childHeightWidth = childHeight->getWidth(); int childHeightHeight = childHeight->getContents().size() / childHeightWidth; if (childHeightWidth != heightWidth || childHeightHeight != heightHeight) { qWarning() << "Height dimension mismatch [heightWidth=" << heightWidth << ", heightHeight=" << heightHeight << ", childHeightWidth=" << childHeightWidth << ", childHeightHeight=" << childHeightHeight << "]"; continue; } int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; int innerQuadrantHeightWidth = innerHeightWidth / 2; int innerQuadrantHeightHeight = innerHeightHeight / 2; int quadrantHeightWidth = innerQuadrantHeightWidth + HeightfieldHeight::HEIGHT_EXTENSION - 1; int quadrantHeightHeight = innerQuadrantHeightHeight + HeightfieldHeight::HEIGHT_EXTENSION - 1; quint16* dest = heightContents.data() + (i & Y_MAXIMUM_FLAG ? (innerQuadrantHeightHeight + 1) * heightWidth : 0) + (i & X_MAXIMUM_FLAG ? innerQuadrantHeightWidth + 1 : 0); const quint16* src = childHeight->getContents().constData(); for (int z = 0; z < quadrantHeightHeight; z++, dest += heightWidth, src += heightWidth * 2) { const quint16* lineSrc = src; for (quint16* lineDest = dest, *end = dest + quadrantHeightWidth; lineDest != end; lineDest++, lineSrc += 2) { *lineDest = *lineSrc; } } } _height = new HeightfieldHeight(heightWidth, heightContents); } else if (height) { _height.reset(); } if (colorWidth > 0 && colorMaterial) { QByteArray colorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF); for (int i = 0; i < CHILD_COUNT; i++) { HeightfieldColorPointer childColor = _children[i]->getColor(); if (!childColor) { continue; } int childColorWidth = childColor->getWidth(); int childColorHeight = childColor->getContents().size() / (childColorWidth * DataBlock::COLOR_BYTES); if (childColorWidth != colorWidth || childColorHeight != colorHeight) { qWarning() << "Color dimension mismatch [colorWidth=" << colorWidth << ", colorHeight=" << colorHeight << ", childColorWidth=" << childColorWidth << ", childColorHeight=" << childColorHeight << "]"; continue; } int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; int innerQuadrantColorWidth = innerColorWidth / 2; int innerQuadrantColorHeight = innerColorHeight / 2; int quadrantColorWidth = innerQuadrantColorWidth + HeightfieldData::SHARED_EDGE; int quadrantColorHeight = innerQuadrantColorHeight + HeightfieldData::SHARED_EDGE; char* dest = colorContents.data() + ((i & Y_MAXIMUM_FLAG ? innerQuadrantColorHeight * colorWidth : 0) + (i & X_MAXIMUM_FLAG ? innerQuadrantColorWidth : 0)) * DataBlock::COLOR_BYTES; const uchar* src = (const uchar*)childColor->getContents().constData(); for (int z = 0; z < quadrantColorHeight; z++, dest += colorWidth * DataBlock::COLOR_BYTES, src += colorWidth * DataBlock::COLOR_BYTES * 2) { const uchar* lineSrc = src; for (char* lineDest = dest, *end = dest + quadrantColorWidth * DataBlock::COLOR_BYTES; lineDest != end; lineDest += DataBlock::COLOR_BYTES, lineSrc += DataBlock::COLOR_BYTES * 2) { lineDest[0] = lineSrc[0]; lineDest[1] = lineSrc[1]; lineDest[2] = lineSrc[2]; } } } _color = new HeightfieldColor(colorWidth, colorContents); } else { _color.reset(); } if (materialWidth > 0 && colorMaterial) { QByteArray materialContents(materialWidth * materialHeight, 0); QVector materials; for (int i = 0; i < CHILD_COUNT; i++) { HeightfieldMaterialPointer childMaterial = _children[i]->getMaterial(); if (!childMaterial) { continue; } int childMaterialWidth = childMaterial->getWidth(); int childMaterialHeight = childMaterial->getContents().size() / childMaterialWidth; if (childMaterialWidth != materialWidth || childMaterialHeight != materialHeight) { qWarning() << "Material dimension mismatch [materialWidth=" << materialWidth << ", materialHeight=" << materialHeight << ", childMaterialWidth=" << childMaterialWidth << ", childMaterialHeight=" << childMaterialHeight << "]"; continue; } int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; int innerQuadrantMaterialWidth = innerMaterialWidth / 2; int innerQuadrantMaterialHeight = innerMaterialHeight / 2; int quadrantMaterialWidth = innerQuadrantMaterialWidth + HeightfieldData::SHARED_EDGE; int quadrantMaterialHeight = innerQuadrantMaterialHeight + HeightfieldData::SHARED_EDGE; uchar* dest = (uchar*)materialContents.data() + (i & Y_MAXIMUM_FLAG ? innerQuadrantMaterialHeight * materialWidth : 0) + (i & X_MAXIMUM_FLAG ? innerQuadrantMaterialWidth : 0); const uchar* src = (const uchar*)childMaterial->getContents().constData(); QHash materialMap; for (int z = 0; z < quadrantMaterialHeight; z++, dest += materialWidth, src += materialWidth * 2) { const uchar* lineSrc = src; for (uchar* lineDest = dest, *end = dest + quadrantMaterialWidth; lineDest != end; lineDest++, lineSrc += 2) { int value = *lineSrc; if (value != 0) { int& mapping = materialMap[value]; if (mapping == 0) { mapping = getMaterialIndex(childMaterial->getMaterials().at(value - 1), materials, materialContents); } value = mapping; } *lineDest = value; } } } _material = new HeightfieldMaterial(materialWidth, materialContents, materials); } else { _material.reset(); } if (stackWidth > 0) { QVector stackContents(stackWidth * stackHeight); QVector stackMaterials; for (int i = 0; i < CHILD_COUNT; i++) { HeightfieldStackPointer childStack = _children[i]->getStack(); if (!childStack) { continue; } int childStackWidth = childStack->getWidth(); int childStackHeight = childStack->getContents().size() / childStackWidth; if (childStackWidth != stackWidth || childStackHeight != stackHeight) { qWarning() << "Stack dimension mismatch [stackWidth=" << stackWidth << ", stackHeight=" << stackHeight << ", childStackWidth=" << childStackWidth << ", childStackHeight=" << childStackHeight << "]"; continue; } int innerStackWidth = stackWidth - HeightfieldData::SHARED_EDGE; int innerStackHeight = stackHeight - HeightfieldData::SHARED_EDGE; int innerQuadrantStackWidth = innerStackWidth / 2; int innerQuadrantStackHeight = innerStackHeight / 2; int quadrantStackWidth = innerQuadrantStackWidth + HeightfieldData::SHARED_EDGE; int quadrantStackHeight = innerQuadrantStackHeight + HeightfieldData::SHARED_EDGE; StackArray* dest = stackContents.data() + (i & Y_MAXIMUM_FLAG ? innerQuadrantStackHeight * stackWidth : 0) + (i & X_MAXIMUM_FLAG ? innerQuadrantStackWidth : 0); const StackArray* src = childStack->getContents().constData(); QHash materialMap; for (int z = 0; z < quadrantStackHeight; z++, dest += stackWidth, src += stackWidth * 2) { const StackArray* lineSrc = src; StackArray* lineDest = dest; for (int x = 0; x < quadrantStackWidth; x++, lineDest++, lineSrc += 2) { if (lineSrc->isEmpty()) { continue; } int minimumY = lineSrc->getPosition(); int maximumY = lineSrc->getPosition() + lineSrc->getEntryCount() - 1; int newMinimumY = minimumY / 2; int newMaximumY = maximumY / 2; *lineDest = StackArray(newMaximumY - newMinimumY + 1); lineDest->setPosition(newMinimumY); int y = newMinimumY; for (StackArray::Entry* destEntry = lineDest->getEntryData(), *end = destEntry + lineDest->getEntryCount(); destEntry != end; destEntry++, y++) { int srcY = y * 2; const StackArray::Entry& srcEntry = lineSrc->getEntry(srcY); destEntry->color = srcEntry.color; destEntry->material = srcEntry.material; if (destEntry->material != 0) { int& mapping = materialMap[destEntry->material]; if (mapping == 0) { mapping = getMaterialIndex(childStack->getMaterials().at(destEntry->material - 1), stackMaterials, stackContents); } destEntry->material = mapping; } int srcAlpha = qAlpha(srcEntry.color); glm::vec3 normal; if (srcAlpha != lineSrc->getEntryAlpha(srcY + 2)) { const StackArray::Entry& nextSrcEntry = lineSrc->getEntry(srcY + 1); if (qAlpha(nextSrcEntry.color) == srcAlpha) { float distance = nextSrcEntry.getHermiteY(normal); destEntry->setHermiteY(normal, distance * 0.5f + 0.5f); } else { float distance = srcEntry.getHermiteY(normal); destEntry->setHermiteY(normal, distance * 0.5f); } } if (x != quadrantStackWidth - 1 && srcAlpha != lineSrc[2].getEntryAlpha(srcY)) { const StackArray::Entry& nextSrcEntry = lineSrc[1].getEntry(srcY); if (qAlpha(nextSrcEntry.color) == srcAlpha) { float distance = nextSrcEntry.getHermiteX(normal); destEntry->setHermiteX(normal, distance * 0.5f + 0.5f); } else { float distance = srcEntry.getHermiteX(normal); destEntry->setHermiteX(normal, distance * 0.5f); } } if (z != quadrantStackHeight - 1 && srcAlpha != lineSrc[2 * stackWidth].getEntryAlpha(srcY)) { const StackArray::Entry& nextSrcEntry = lineSrc[stackWidth].getEntry(srcY); if (qAlpha(nextSrcEntry.color) == srcAlpha) { float distance = nextSrcEntry.getHermiteZ(normal); destEntry->setHermiteZ(normal, distance * 0.5f + 0.5f); } else { float distance = srcEntry.getHermiteZ(normal); destEntry->setHermiteZ(normal, distance * 0.5f); } } } } } } _stack = new HeightfieldStack(stackWidth, stackContents, stackMaterials); } else { _stack.reset(); } } QRgb HeightfieldNode::getColorAt(const glm::vec3& location) const { if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { return 0; } int width = _color->getWidth(); const QByteArray& contents = _color->getContents(); const uchar* src = (const uchar*)contents.constData(); int height = contents.size() / (width * DataBlock::COLOR_BYTES); int innerWidth = width - HeightfieldData::SHARED_EDGE; int innerHeight = height - HeightfieldData::SHARED_EDGE; glm::vec3 relative = location * glm::vec3((float)innerWidth, 1.0f, (float)innerHeight); 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* 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 HeightfieldNode::getMaterialAt(const glm::vec3& location) const { if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { return -1; } int width = _material->getWidth(); const QByteArray& contents = _material->getContents(); const uchar* src = (const uchar*)contents.constData(); int height = contents.size() / width; int innerWidth = width - HeightfieldData::SHARED_EDGE; int innerHeight = height - HeightfieldData::SHARED_EDGE; glm::vec3 relative = location * glm::vec3((float)innerWidth, 1.0f, (float)innerHeight); return src[(int)glm::round(relative.z) * width + (int)glm::round(relative.x)]; } void HeightfieldNode::maybeRenormalize(const glm::vec3& scale, float normalizeScale, float normalizeOffset, int innerStackWidth, QVector& heightContents, QVector& stackContents) { if (normalizeScale == 1.0f && normalizeOffset == 0.0f) { return; } for (quint16* dest = heightContents.data(), *end = dest + heightContents.size(); dest != end; dest++) { int value = *dest; if (value != 0) { *dest = (value + normalizeOffset) * normalizeScale; } } if (stackContents.isEmpty()) { return; } int stackOffset = glm::round(scale.y * normalizeOffset * normalizeScale * innerStackWidth / (numeric_limits::max() * scale.x)); for (StackArray* dest = stackContents.data(), *end = dest + stackContents.size(); dest != end; dest++) { if (!dest->isEmpty()) { dest->getPositionRef() += stackOffset; } } } bool HeightfieldNode::findHeightfieldRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const { if (!_height) { return false; } int width = _height->getWidth(); const QVector& contents = _height->getContents(); const quint16* src = contents.constData(); int height = contents.size() / width; int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION; int highestX = innerWidth + HeightfieldHeight::HEIGHT_BORDER; int highestZ = innerHeight + HeightfieldHeight::HEIGHT_BORDER; glm::vec3 heightScale((float)innerWidth, (float)numeric_limits::max(), (float)innerHeight); glm::vec3 dir = direction * heightScale; glm::vec3 entry = origin * heightScale + dir * boundsDistance; entry.x += HeightfieldHeight::HEIGHT_BORDER; entry.z += HeightfieldHeight::HEIGHT_BORDER; glm::vec3 floors = glm::floor(entry); glm::vec3 ceils = glm::ceil(entry); if (floors.x == ceils.x) { if (dir.x > 0.0f) { ceils.x += 1.0f; } else { floors.x -= 1.0f; } } if (floors.z == ceils.z) { if (dir.z > 0.0f) { ceils.z += 1.0f; } else { floors.z -= 1.0f; } } bool withinBounds = true; float accumulatedDistance = 0.0f; while (withinBounds) { // find the heights at the corners of the current cell int floorX = qMin(qMax((int)floors.x, HeightfieldHeight::HEIGHT_BORDER), highestX); int floorZ = qMin(qMax((int)floors.z, HeightfieldHeight::HEIGHT_BORDER), highestZ); int ceilX = qMin(qMax((int)ceils.x, HeightfieldHeight::HEIGHT_BORDER), highestX); int ceilZ = qMin(qMax((int)ceils.z, HeightfieldHeight::HEIGHT_BORDER), highestZ); float upperLeft = src[floorZ * width + floorX]; float upperRight = src[floorZ * width + ceilX]; float lowerLeft = src[ceilZ * width + floorX]; float lowerRight = src[ceilZ * width + ceilX]; // find the distance to the next x coordinate float xDistance = FLT_MAX; if (dir.x > 0.0f) { xDistance = (ceils.x - entry.x) / dir.x; } else if (dir.x < 0.0f) { xDistance = (floors.x - entry.x) / dir.x; } // and the distance to the next z coordinate float zDistance = FLT_MAX; if (dir.z > 0.0f) { zDistance = (ceils.z - entry.z) / dir.z; } else if (dir.z < 0.0f) { zDistance = (floors.z - entry.z) / dir.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) { if (dir.y > 0.0f) { return false; // line points upwards; no collisions possible } withinBounds = false; // line points 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 * dir; withinBounds = (exit.y >= 0.0f && exit.y <= numeric_limits::max()); if (exitDistance == xDistance) { if (dir.x > 0.0f) { nextFloors.x += 1.0f; withinBounds &= (nextCeils.x += 1.0f) <= highestX; } else { withinBounds &= (nextFloors.x -= 1.0f) >= HeightfieldHeight::HEIGHT_BORDER; nextCeils.x -= 1.0f; } } if (exitDistance == zDistance) { if (dir.z > 0.0f) { nextFloors.z += 1.0f; withinBounds &= (nextCeils.z += 1.0f) <= highestZ; } else { withinBounds &= (nextFloors.z -= 1.0f) >= HeightfieldHeight::HEIGHT_BORDER; nextCeils.z -= 1.0f; } } // check the vertical range of the ray against the ranges of the cell heights if (upperLeft == 0 || upperRight == 0 || lowerLeft == 0 || lowerRight == 0 || 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, dir); if (lowerProduct < 0.0f) { float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct; glm::vec3 intersection = relativeEntry + planeDistance * dir; if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && intersection.z >= intersection.x) { distance = boundsDistance + accumulatedDistance + planeDistance; return true; } } // then the one with the X+ segment glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight); float upperProduct = glm::dot(upperNormal, dir); if (upperProduct < 0.0f) { float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct; glm::vec3 intersection = relativeEntry + planeDistance * dir; if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && intersection.x >= intersection.z) { distance = boundsDistance + accumulatedDistance + planeDistance; return true; } } // no joy; continue on our way entry = exit; floors = nextFloors; ceils = nextCeils; accumulatedDistance += exitDistance; } return false; } AbstractHeightfieldNodeRenderer::~AbstractHeightfieldNodeRenderer() { } bool AbstractHeightfieldNodeRenderer::findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const { return false; } Heightfield::Heightfield() : _aspectY(1.0f), _aspectZ(1.0f) { connect(this, &Heightfield::translationChanged, this, &Heightfield::updateBounds); connect(this, &Heightfield::rotationChanged, this, &Heightfield::updateBounds); connect(this, &Heightfield::scaleChanged, this, &Heightfield::updateBounds); connect(this, &Heightfield::aspectYChanged, this, &Heightfield::updateBounds); connect(this, &Heightfield::aspectZChanged, this, &Heightfield::updateBounds); updateBounds(); connect(this, &Heightfield::heightChanged, this, &Heightfield::updateRoot); connect(this, &Heightfield::colorChanged, this, &Heightfield::updateRoot); connect(this, &Heightfield::materialChanged, this, &Heightfield::updateRoot); connect(this, &Heightfield::stackChanged, this, &Heightfield::updateRoot); updateRoot(); } void Heightfield::setAspectY(float aspectY) { if (_aspectY != aspectY) { emit aspectYChanged(_aspectY = aspectY); } } void Heightfield::setAspectZ(float aspectZ) { if (_aspectZ != aspectZ) { emit aspectZChanged(_aspectZ = aspectZ); } } void Heightfield::setHeight(const HeightfieldHeightPointer& height) { if (_height != height) { emit heightChanged(_height = height); } } void Heightfield::setColor(const HeightfieldColorPointer& color) { if (_color != color) { emit colorChanged(_color = color); } } void Heightfield::setMaterial(const HeightfieldMaterialPointer& material) { if (_material != material) { emit materialChanged(_material = material); } } void Heightfield::setStack(const HeightfieldStackPointer& stack) { if (_stack != stack) { emit stackChanged(_stack = stack); } } MetavoxelLOD Heightfield::transformLOD(const MetavoxelLOD& lod) const { // after transforming into unit space, we scale the threshold in proportion to vertical distance glm::vec3 inverseScale(1.0f / getScale(), 1.0f / (getScale() * _aspectY), 1.0f / (getScale() * _aspectZ)); glm::vec3 position = glm::inverse(getRotation()) * (lod.position - getTranslation()) * inverseScale; const float THRESHOLD_MULTIPLIER = 256.0f; return MetavoxelLOD(glm::vec3(position.x, position.z, 0.0f), lod.threshold * qMax(0.5f, glm::abs(position.y * _aspectY - 0.5f)) * THRESHOLD_MULTIPLIER); } SharedObject* Heightfield::clone(bool withID, SharedObject* target) const { Heightfield* newHeightfield = static_cast(Spanner::clone(withID, target)); newHeightfield->setHeight(_height); newHeightfield->setColor(_color); newHeightfield->setMaterial(_material); newHeightfield->setRoot(_root); return newHeightfield; } bool Heightfield::isHeightfield() const { return true; } float Heightfield::getHeight(const glm::vec3& location) const { float distance; glm::vec3 down = getRotation() * glm::vec3(0.0f, -1.0f, 0.0f); glm::vec3 origin = location - down * (glm::dot(down, location) + getScale() * _aspectY - glm::dot(down, getTranslation())); if (findRayIntersection(origin, down, distance)) { return origin.y + distance * down.y; } return -FLT_MAX; } bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { return _root->findRayIntersection(getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), origin, direction, distance); } Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase) { // first see if we're going to exceed the range limits float minimumValue = 1.0f, maximumValue = numeric_limits::max(); if (set) { float heightValue = height * numeric_limits::max() / (getScale() * _aspectY); minimumValue = qMin(minimumValue, heightValue); maximumValue = qMax(maximumValue, heightValue); } else if (!erase) { _root->getRangeAfterHeightPaint(getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), position, radius, height, minimumValue, maximumValue); } // normalize if necessary float normalizeScale, normalizeOffset; Heightfield* newHeightfield = prepareEdit(minimumValue, maximumValue, normalizeScale, normalizeOffset); newHeightfield->setRoot(HeightfieldNodePointer(_root->paintHeight(newHeightfield->getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), position, radius, height, set, erase, normalizeScale, normalizeOffset))); return newHeightfield; } Spanner* Heightfield::fillHeight(const glm::vec3& position, float radius) { Heightfield* newHeightfield = static_cast(clone(true)); newHeightfield->setRoot(HeightfieldNodePointer(_root->fillHeight(getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), position, radius))); return newHeightfield; } Spanner* Heightfield::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize) { // first see if we're going to exceed the range limits, normalizing if necessary Spanner* spannerData = static_cast(spanner.data()); float normalizeScale = 1.0f, normalizeOffset = 0.0f; Heightfield* newHeightfield; if (paint) { newHeightfield = static_cast(clone(true)); } else { float minimumValue = 1.0f, maximumValue = numeric_limits::max(); _root->getRangeAfterEdit(getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), spannerData->getBounds(), minimumValue, maximumValue); newHeightfield = prepareEdit(minimumValue, maximumValue, normalizeScale, normalizeOffset); } newHeightfield->setRoot(HeightfieldNodePointer(_root->setMaterial(newHeightfield->getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), spannerData, material, color, paint, voxelize, normalizeScale, normalizeOffset))); return newHeightfield; } bool Heightfield::hasOwnColors() const { return _color; } bool Heightfield::hasOwnMaterials() const { return _material; } QRgb Heightfield::getColorAt(const glm::vec3& point) { int width = _color->getWidth(); const QByteArray& contents = _color->getContents(); const uchar* src = (const uchar*)contents.constData(); int height = contents.size() / (width * DataBlock::COLOR_BYTES); int innerWidth = width - HeightfieldData::SHARED_EDGE; int innerHeight = height - HeightfieldData::SHARED_EDGE; glm::vec3 relative = glm::inverse(getRotation()) * (point - getTranslation()) * glm::vec3(innerWidth / getScale(), 1.0f, innerHeight / (getScale() * _aspectZ)); if (relative.x < 0.0f || relative.z < 0.0f || relative.x > width - 1 || relative.z > height - 1) { return 0; } 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* 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::getMaterialAt(const glm::vec3& point) { int width = _material->getWidth(); const QByteArray& contents = _material->getContents(); const uchar* src = (const uchar*)contents.constData(); int height = contents.size() / width; int innerWidth = width - HeightfieldData::SHARED_EDGE; int innerHeight = height - HeightfieldData::SHARED_EDGE; glm::vec3 relative = glm::inverse(getRotation()) * (point - getTranslation()) * glm::vec3(innerWidth / getScale(), 1.0f, innerHeight / (getScale() * _aspectZ)); if (relative.x < 0.0f || relative.z < 0.0f || relative.x > width - 1 || relative.z > height - 1) { return -1; } return src[(int)glm::round(relative.z) * width + (int)glm::round(relative.x)]; } QVector& Heightfield::getMaterials() { return _material->getMaterials(); } bool Heightfield::contains(const glm::vec3& point) { if (!_height) { return false; } int width = _height->getWidth(); const QVector& contents = _height->getContents(); const quint16* src = contents.constData(); int height = contents.size() / width; int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION; int highestX = innerWidth + HeightfieldHeight::HEIGHT_BORDER; int highestZ = innerHeight + HeightfieldHeight::HEIGHT_BORDER; glm::vec3 relative = glm::inverse(getRotation()) * (point - getTranslation()) * glm::vec3(innerWidth / getScale(), numeric_limits::max() / (getScale() * _aspectY), innerHeight / (getScale() * _aspectZ)); if (relative.x < 0.0f || relative.y < 0.0f || relative.z < 0.0f || relative.x > innerWidth || relative.y > numeric_limits::max() || relative.z > innerHeight) { return false; } relative.x += HeightfieldHeight::HEIGHT_BORDER; relative.z += HeightfieldHeight::HEIGHT_BORDER; // find the bounds of the cell containing the point and the shared vertex heights glm::vec3 floors = glm::floor(relative); glm::vec3 ceils = glm::ceil(relative); glm::vec3 fracts = glm::fract(relative); int floorX = qMin(qMax((int)floors.x, HeightfieldHeight::HEIGHT_BORDER), highestX); int floorZ = qMin(qMax((int)floors.z, HeightfieldHeight::HEIGHT_BORDER), highestZ); int ceilX = qMin(qMax((int)ceils.x, HeightfieldHeight::HEIGHT_BORDER), highestX); int ceilZ = qMin(qMax((int)ceils.z, HeightfieldHeight::HEIGHT_BORDER), highestZ); 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); } if (interpolatedHeight == 0.0f) { return false; // ignore zero values } // compare return relative.y <= interpolatedHeight; } bool Heightfield::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { int width = _height->getWidth(); const QVector& contents = _height->getContents(); const quint16* src = contents.constData(); int height = contents.size() / width; int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION; int highestX = innerWidth + HeightfieldHeight::HEIGHT_BORDER; int highestZ = innerHeight + HeightfieldHeight::HEIGHT_BORDER; glm::quat inverseRotation = glm::inverse(getRotation()); glm::vec3 inverseScale(innerWidth / getScale(), numeric_limits::max() / (getScale() * _aspectY), innerHeight / (getScale() * _aspectZ)); glm::vec3 direction = end - start; glm::vec3 dir = inverseRotation * direction * inverseScale; glm::vec3 entry = inverseRotation * (start - getTranslation()) * inverseScale; float boundsDistance; if (!Box(glm::vec3(), glm::vec3((float)innerWidth, (float)numeric_limits::max(), (float)innerHeight)).findRayIntersection(entry, dir, boundsDistance) || boundsDistance > 1.0f) { return false; } entry += dir * boundsDistance; const float DISTANCE_THRESHOLD = 0.001f; if (glm::abs(entry.x - 0.0f) < DISTANCE_THRESHOLD) { normal = getRotation() * glm::vec3(-1.0f, 0.0f, 0.0f); distance = boundsDistance; return true; } else if (glm::abs(entry.x - innerWidth) < DISTANCE_THRESHOLD) { normal = getRotation() * glm::vec3(1.0f, 0.0f, 0.0f); distance = boundsDistance; return true; } else if (glm::abs(entry.y - 0.0f) < DISTANCE_THRESHOLD) { normal = getRotation() * glm::vec3(0.0f, -1.0f, 0.0f); distance = boundsDistance; return true; } else if (glm::abs(entry.y - numeric_limits::max()) < DISTANCE_THRESHOLD) { normal = getRotation() * glm::vec3(0.0f, 1.0f, 0.0f); distance = boundsDistance; return true; } else if (glm::abs(entry.z - 0.0f) < DISTANCE_THRESHOLD) { normal = getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); distance = boundsDistance; return true; } else if (glm::abs(entry.z - innerHeight) < DISTANCE_THRESHOLD) { normal = getRotation() * glm::vec3(0.0f, 0.0f, 1.0f); distance = boundsDistance; return true; } entry.x += HeightfieldHeight::HEIGHT_BORDER; entry.z += HeightfieldHeight::HEIGHT_BORDER; glm::vec3 floors = glm::floor(entry); glm::vec3 ceils = glm::ceil(entry); if (floors.x == ceils.x) { if (dir.x > 0.0f) { ceils.x += 1.0f; } else { floors.x -= 1.0f; } } if (floors.z == ceils.z) { if (dir.z > 0.0f) { ceils.z += 1.0f; } else { floors.z -= 1.0f; } } glm::vec3 normalScale(1.0f / (inverseScale.y * inverseScale.z), 1.0f / (inverseScale.x * inverseScale.z), 1.0f / (inverseScale.x * inverseScale.y)); bool withinBounds = true; float accumulatedDistance = boundsDistance; while (withinBounds && accumulatedDistance <= 1.0f) { // find the heights at the corners of the current cell int floorX = qMin(qMax((int)floors.x, HeightfieldHeight::HEIGHT_BORDER), highestX); int floorZ = qMin(qMax((int)floors.z, HeightfieldHeight::HEIGHT_BORDER), highestZ); int ceilX = qMin(qMax((int)ceils.x, HeightfieldHeight::HEIGHT_BORDER), highestX); int ceilZ = qMin(qMax((int)ceils.z, HeightfieldHeight::HEIGHT_BORDER), highestZ); float upperLeft = src[floorZ * width + floorX]; float upperRight = src[floorZ * width + ceilX]; float lowerLeft = src[ceilZ * width + floorX]; float lowerRight = src[ceilZ * width + ceilX]; // find the distance to the next x coordinate float xDistance = FLT_MAX; if (dir.x > 0.0f) { xDistance = (ceils.x - entry.x) / dir.x; } else if (dir.x < 0.0f) { xDistance = (floors.x - entry.x) / dir.x; } // and the distance to the next z coordinate float zDistance = FLT_MAX; if (dir.z > 0.0f) { zDistance = (ceils.z - entry.z) / dir.z; } else if (dir.z < 0.0f) { zDistance = (floors.z - entry.z) / dir.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 * dir; withinBounds = (exit.y >= 0.0f && exit.y <= numeric_limits::max()); if (exitDistance == xDistance) { if (dir.x > 0.0f) { nextFloors.x += 1.0f; withinBounds &= (nextCeils.x += 1.0f) <= highestX; } else { withinBounds &= (nextFloors.x -= 1.0f) >= HeightfieldHeight::HEIGHT_BORDER; nextCeils.x -= 1.0f; } } if (exitDistance == zDistance) { if (dir.z > 0.0f) { nextFloors.z += 1.0f; withinBounds &= (nextCeils.z += 1.0f) <= highestZ; } else { withinBounds &= (nextFloors.z -= 1.0f) >= HeightfieldHeight::HEIGHT_BORDER; 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, dir); if (lowerProduct != 0.0f) { float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct; glm::vec3 intersection = relativeEntry + planeDistance * dir; if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && intersection.z >= intersection.x) { distance = accumulatedDistance + planeDistance; normal = glm::normalize(getRotation() * (lowerNormal * normalScale)); return true; } } // then the one with the X+ segment glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight); float upperProduct = glm::dot(upperNormal, dir); if (upperProduct != 0.0f) { float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct; glm::vec3 intersection = relativeEntry + planeDistance * dir; if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && intersection.x >= intersection.z) { distance = accumulatedDistance + planeDistance; normal = glm::normalize(getRotation() * (upperNormal * normalScale)); return true; } } // no joy; continue on our way entry = exit; floors = nextFloors; ceils = nextCeils; accumulatedDistance += exitDistance; } return false; } void Heightfield::writeExtra(Bitstream& out) const { if (getWillBeVoxelized()) { out << _height << _color << _material << _stack; return; } MetavoxelLOD lod; if (out.getContext()) { lod = transformLOD(static_cast(out.getContext())->lod); } HeightfieldStreamBase base = { out, lod, lod }; HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; _root->write(state); } void Heightfield::readExtra(Bitstream& in, bool reread) { if (getWillBeVoxelized()) { if (reread) { HeightfieldHeightPointer height; HeightfieldColorPointer color; HeightfieldMaterialPointer material; HeightfieldStackPointer stack; in >> height >> color >> material >> stack; } else { in >> _height >> _color >> _material >> _stack; } return; } MetavoxelLOD lod; if (in.getContext()) { lod = transformLOD(static_cast(in.getContext())->lod); } HeightfieldStreamBase base = { in, lod, lod }; HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; HeightfieldNodePointer root(new HeightfieldNode()); root->read(state); if (!reread) { setRoot(root); } } void Heightfield::writeExtraDelta(Bitstream& out, const SharedObject* reference) const { MetavoxelLOD lod, referenceLOD; if (out.getContext()) { MetavoxelStreamBase* base = static_cast(out.getContext()); lod = transformLOD(base->lod); referenceLOD = transformLOD(base->referenceLOD); } HeightfieldStreamBase base = { out, lod, referenceLOD }; HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; const HeightfieldNodePointer& referenceRoot = static_cast(reference)->getRoot(); if (_root == referenceRoot) { out << false; if (state.becameSubdivided()) { _root->writeSubdivision(state); } } else { out << true; _root->writeDelta(referenceRoot, state); } } void Heightfield::readExtraDelta(Bitstream& in, const SharedObject* reference, bool reread) { MetavoxelLOD lod, referenceLOD; if (in.getContext()) { MetavoxelStreamBase* base = static_cast(in.getContext()); lod = transformLOD(base->lod); referenceLOD = transformLOD(base->referenceLOD); } HeightfieldStreamBase base = { in, lod, referenceLOD }; HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; bool changed; in >> changed; if (changed) { HeightfieldNodePointer root(new HeightfieldNode()); root->readDelta(static_cast(reference)->getRoot(), state); if (!reread) { setRoot(root); } } else if (state.becameSubdividedOrCollapsed()) { HeightfieldNodePointer root(_root->readSubdivision(state)); if (!reread) { setRoot(root); } } else if (!reread) { setRoot(static_cast(reference)->getRoot()); } } void Heightfield::maybeWriteSubdivision(Bitstream& out) { MetavoxelLOD lod, referenceLOD; if (out.getContext()) { MetavoxelStreamBase* base = static_cast(out.getContext()); lod = transformLOD(base->lod); referenceLOD = transformLOD(base->referenceLOD); } HeightfieldStreamBase base = { out, lod, referenceLOD }; HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; if (state.becameSubdividedOrCollapsed()) { out << SharedObjectPointer(this); _root->writeSubdivision(state); } } SharedObject* Heightfield::readSubdivision(Bitstream& in) { MetavoxelLOD lod, referenceLOD; if (in.getContext()) { MetavoxelStreamBase* base = static_cast(in.getContext()); lod = transformLOD(base->lod); referenceLOD = transformLOD(base->referenceLOD); } HeightfieldStreamBase base = { in, lod, referenceLOD }; HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; if (state.becameSubdividedOrCollapsed()) { HeightfieldNodePointer root(_root->readSubdivision(state)); if (_root != root) { Heightfield* newHeightfield = static_cast(clone(true)); newHeightfield->setRemoteID(getRemoteID()); newHeightfield->setRemoteOriginID(getRemoteOriginID()); newHeightfield->setRoot(root); return newHeightfield; } } return this; } QByteArray Heightfield::getRendererClassName() const { return "HeightfieldRenderer"; } void Heightfield::updateBounds() { glm::vec3 extent(getScale(), getScale() * _aspectY, getScale() * _aspectZ); glm::mat4 rotationMatrix = glm::mat4_cast(getRotation()); setBounds(glm::translate(getTranslation()) * rotationMatrix * Box(glm::vec3(), extent)); } void Heightfield::updateRoot() { HeightfieldNodePointer root(new HeightfieldNode()); if (_height) { root->setContents(_height, _color, _material, _stack); } setRoot(root); } Heightfield* Heightfield::prepareEdit(float minimumValue, float maximumValue, float& normalizeScale, float& normalizeOffset) { // renormalize if necessary Heightfield* newHeightfield = static_cast(clone(true)); if (minimumValue < 1.0f || maximumValue > numeric_limits::max()) { normalizeScale = (numeric_limits::max() - 1.0f) / (maximumValue - minimumValue); normalizeOffset = 1.0f - minimumValue; newHeightfield->setAspectY(_aspectY / normalizeScale); newHeightfield->setTranslation(getTranslation() - getRotation() * glm::vec3(0.0f, normalizeOffset * _aspectY * getScale() / (numeric_limits::max() - 1), 0.0f)); } else { normalizeScale = 1.0f; normalizeOffset = 0.0f; } return newHeightfield; }