From 0664ff3181378a820283e93ac923c6949b16f6f5 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 10 Nov 2014 15:51:41 -0800 Subject: [PATCH] Moved spanners into separate file, working on heightfields as spanners. --- interface/src/MetavoxelSystem.cpp | 8 + interface/src/MetavoxelSystem.h | 11 + .../metavoxels/src/AttributeRegistry.cpp | 2 + libraries/metavoxels/src/Endpoint.h | 1 + libraries/metavoxels/src/MetavoxelData.cpp | 557 +------------ libraries/metavoxels/src/MetavoxelData.h | 279 ------- .../metavoxels/src/MetavoxelMessages.cpp | 5 +- libraries/metavoxels/src/Spanner.cpp | 783 ++++++++++++++++++ libraries/metavoxels/src/Spanner.h | 454 ++++++++++ libraries/networking/src/PacketHeaders.cpp | 2 +- 10 files changed, 1264 insertions(+), 838 deletions(-) create mode 100644 libraries/metavoxels/src/Spanner.cpp create mode 100644 libraries/metavoxels/src/Spanner.h diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index a671452143..d2b3e20122 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -36,6 +36,7 @@ REGISTER_META_OBJECT(DefaultMetavoxelRendererImplementation) REGISTER_META_OBJECT(SphereRenderer) REGISTER_META_OBJECT(CuboidRenderer) REGISTER_META_OBJECT(StaticModelRenderer) +REGISTER_META_OBJECT(HeightfieldRenderer) MetavoxelSystem::NetworkSimulation::NetworkSimulation(float dropRate, float repeatRate, int minimumDelay, int maximumDelay, int bandwidthLimit) : @@ -2854,6 +2855,7 @@ ProgramObject DefaultMetavoxelRendererImplementation::_voxelCursorProgram; SphereRenderer::SphereRenderer() { } + void SphereRenderer::render(const glm::vec4& color, Mode mode) { Sphere* sphere = static_cast(_spanner); const QColor& ownColor = sphere->getColor(); @@ -2969,3 +2971,9 @@ void StaticModelRenderer::applyScale(float scale) { void StaticModelRenderer::applyURL(const QUrl& url) { _model->setURL(url); } + +HeightfieldRenderer::HeightfieldRenderer() { +} + +void HeightfieldRenderer::render(const glm::vec4& color, Mode mode) { +} diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index e0fce70c2d..12d5b5bca8 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -450,4 +450,15 @@ private: Model* _model; }; +/// Renders heightfields. +class HeightfieldRenderer : public SpannerRenderer { + Q_OBJECT + +public: + + Q_INVOKABLE HeightfieldRenderer(); + + virtual void render(const glm::vec4& color, Mode mode); +}; + #endif // hifi_MetavoxelSystem_h diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 0ed20386c6..ddc23980b5 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -17,6 +17,7 @@ #include "AttributeRegistry.h" #include "MetavoxelData.h" +#include "Spanner.h" REGISTER_META_OBJECT(FloatAttribute) REGISTER_META_OBJECT(MaterialObject) @@ -54,6 +55,7 @@ AttributeRegistry::AttributeRegistry() : // our baseline LOD threshold is for voxels; spanners and heightfields are a different story const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f; _spannersAttribute->setLODThresholdMultiplier(SPANNER_LOD_THRESHOLD_MULTIPLIER); + _spannersAttribute->setUserFacing(true); const float HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER = 32.0f; _heightfieldAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); diff --git a/libraries/metavoxels/src/Endpoint.h b/libraries/metavoxels/src/Endpoint.h index 816a1fe2a9..c64f29878d 100644 --- a/libraries/metavoxels/src/Endpoint.h +++ b/libraries/metavoxels/src/Endpoint.h @@ -16,6 +16,7 @@ #include "DatagramSequencer.h" #include "MetavoxelData.h" +#include "Spanner.h" class PacketRecord; diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index de9d4c104b..cb3c5b94bf 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -11,24 +11,15 @@ #include #include -#include #include -#include - -#include - #include "MetavoxelData.h" -#include "MetavoxelUtil.h" +#include "Spanner.h" REGISTER_META_OBJECT(MetavoxelGuide) REGISTER_META_OBJECT(DefaultMetavoxelGuide) REGISTER_META_OBJECT(MetavoxelRenderer) REGISTER_META_OBJECT(DefaultMetavoxelRenderer) -REGISTER_META_OBJECT(Spanner) -REGISTER_META_OBJECT(Sphere) -REGISTER_META_OBJECT(Cuboid) -REGISTER_META_OBJECT(StaticModel) static int metavoxelDataTypeId = registerSimpleMetaType(); @@ -1800,549 +1791,3 @@ QByteArray DefaultMetavoxelRenderer::getImplementationClassName() const { return "DefaultMetavoxelRendererImplementation"; } -const float DEFAULT_PLACEMENT_GRANULARITY = 0.01f; -const float DEFAULT_VOXELIZATION_GRANULARITY = powf(2.0f, -3.0f); - -Spanner::Spanner() : - _renderer(NULL), - _placementGranularity(DEFAULT_PLACEMENT_GRANULARITY), - _voxelizationGranularity(DEFAULT_VOXELIZATION_GRANULARITY), - _merged(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::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { - return _bounds.findRayIntersection(origin, direction, distance); -} - -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 glm::vec4& color, Mode mode) { - // 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"; -} - -const float EIGHT_BIT_MAXIMUM = 255.0f; - -Heightfield::Heightfield(const Box& bounds, float increment, const QByteArray& height, const QByteArray& color, - const QByteArray& material, const QVector& materials) : - _increment(increment), - _width((int)glm::round((bounds.maximum.x - bounds.minimum.x) / increment) + 1), - _heightScale((bounds.maximum.y - bounds.minimum.y) / EIGHT_BIT_MAXIMUM), - _height(height), - _color(color), - _material(material), - _materials(materials) { - - setBounds(bounds); -} - -bool Heightfield::hasOwnColors() const { - return true; -} - -bool Heightfield::hasOwnMaterials() const { - return true; -} - -QRgb Heightfield::getColorAt(const glm::vec3& point) { - glm::vec3 relative = (point - getBounds().minimum) / _increment; - glm::vec3 floors = glm::floor(relative); - glm::vec3 ceils = glm::ceil(relative); - glm::vec3 fracts = glm::fract(relative); - int floorX = (int)floors.x; - int floorZ = (int)floors.z; - int ceilX = (int)ceils.x; - int ceilZ = (int)ceils.z; - const uchar* src = (const uchar*)_color.constData(); - const uchar* upperLeft = src + (floorZ * _width + floorX) * DataBlock::COLOR_BYTES; - const uchar* lowerRight = src + (ceilZ * _width + ceilX) * DataBlock::COLOR_BYTES; - glm::vec3 interpolatedColor = glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), - glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z); - - // the final vertex (and thus which triangle we check) depends on which half we're on - if (fracts.x >= fracts.z) { - const uchar* upperRight = src + (floorZ * _width + ceilX) * DataBlock::COLOR_BYTES; - interpolatedColor = glm::mix(interpolatedColor, glm::mix(glm::vec3(upperRight[0], upperRight[1], upperRight[2]), - glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z), (fracts.x - fracts.z) / (1.0f - fracts.z)); - - } else { - const uchar* lowerLeft = src + (ceilZ * _width + floorX) * DataBlock::COLOR_BYTES; - interpolatedColor = glm::mix(glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), - glm::vec3(lowerLeft[0], lowerLeft[1], lowerLeft[2]), fracts.z), interpolatedColor, fracts.x / fracts.z); - } - return qRgb(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b); -} - -int Heightfield::getMaterialAt(const glm::vec3& point) { - glm::vec3 relative = (point - getBounds().minimum) / _increment; - const uchar* src = (const uchar*)_material.constData(); - return src[(int)glm::round(relative.z) * _width + (int)glm::round(relative.x)]; -} - -QVector& Heightfield::getMaterials() { - return _materials; -} - -bool Heightfield::contains(const glm::vec3& point) { - if (!getBounds().contains(point)) { - return false; - } - glm::vec3 relative = (point - getBounds().minimum) / _increment; - glm::vec3 floors = glm::floor(relative); - glm::vec3 ceils = glm::ceil(relative); - glm::vec3 fracts = glm::fract(relative); - int floorX = (int)floors.x; - int floorZ = (int)floors.z; - int ceilX = (int)ceils.x; - int ceilZ = (int)ceils.z; - const uchar* src = (const uchar*)_height.constData(); - float upperLeft = src[floorZ * _width + floorX]; - float lowerRight = src[ceilZ * _width + ceilX]; - float interpolatedHeight = glm::mix(upperLeft, lowerRight, fracts.z); - - // the final vertex (and thus which triangle we check) depends on which half we're on - if (fracts.x >= fracts.z) { - float upperRight = src[floorZ * _width + ceilX]; - interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z), - (fracts.x - fracts.z) / (1.0f - fracts.z)); - - } else { - float lowerLeft = src[ceilZ * _width + floorX]; - interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, fracts.x / fracts.z); - } - return interpolatedHeight != 0.0f && point.y <= interpolatedHeight * _heightScale + getBounds().minimum.y; -} - -bool Heightfield::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { - // find the initial location in heightfield coordinates - float rayDistance; - glm::vec3 direction = end - start; - if (!getBounds().findRayIntersection(start, direction, rayDistance) || rayDistance > 1.0f) { - return false; - } - glm::vec3 entry = start + direction * rayDistance; - const float DISTANCE_THRESHOLD = 0.001f; - if (glm::abs(entry.x - getBounds().minimum.x) < DISTANCE_THRESHOLD) { - normal = glm::vec3(-1.0f, 0.0f, 0.0f); - distance = rayDistance; - return true; - - } else if (glm::abs(entry.x - getBounds().maximum.x) < DISTANCE_THRESHOLD) { - normal = glm::vec3(1.0f, 0.0f, 0.0f); - distance = rayDistance; - return true; - - } else if (glm::abs(entry.y - getBounds().minimum.y) < DISTANCE_THRESHOLD) { - normal = glm::vec3(0.0f, -1.0f, 0.0f); - distance = rayDistance; - return true; - - } else if (glm::abs(entry.y - getBounds().maximum.y) < DISTANCE_THRESHOLD) { - normal = glm::vec3(0.0f, 1.0f, 0.0f); - distance = rayDistance; - return true; - - } else if (glm::abs(entry.z - getBounds().minimum.z) < DISTANCE_THRESHOLD) { - normal = glm::vec3(0.0f, 0.0f, -1.0f); - distance = rayDistance; - return true; - - } else if (glm::abs(entry.z - getBounds().maximum.z) < DISTANCE_THRESHOLD) { - normal = glm::vec3(0.0f, 0.0f, 1.0f); - distance = rayDistance; - return true; - } - entry = (entry - getBounds().minimum) / _increment; - glm::vec3 floors = glm::floor(entry); - glm::vec3 ceils = glm::ceil(entry); - if (floors.x == ceils.x) { - if (direction.x > 0.0f) { - ceils.x += 1.0f; - } else { - floors.x -= 1.0f; - } - } - if (floors.z == ceils.z) { - if (direction.z > 0.0f) { - ceils.z += 1.0f; - } else { - floors.z -= 1.0f; - } - } - - bool withinBounds = true; - float accumulatedDistance = 0.0f; - const uchar* src = (const uchar*)_height.constData(); - int highestX = _width - 1; - float highestY = (getBounds().maximum.y - getBounds().minimum.y) / _increment; - int highestZ = (int)glm::round((getBounds().maximum.z - getBounds().minimum.z) / _increment); - float heightScale = _heightScale / _increment; - while (withinBounds && accumulatedDistance <= 1.0f) { - // find the heights at the corners of the current cell - int floorX = qMin(qMax((int)floors.x, 0), highestX); - int floorZ = qMin(qMax((int)floors.z, 0), highestZ); - int ceilX = qMin(qMax((int)ceils.x, 0), highestX); - int ceilZ = qMin(qMax((int)ceils.z, 0), highestZ); - float upperLeft = src[floorZ * _width + floorX] * heightScale; - float upperRight = src[floorZ * _width + ceilX] * heightScale; - float lowerLeft = src[ceilZ * _width + floorX] * heightScale; - float lowerRight = src[ceilZ * _width + ceilX] * heightScale; - - // find the distance to the next x coordinate - float xDistance = FLT_MAX; - if (direction.x > 0.0f) { - xDistance = (ceils.x - entry.x) / direction.x; - } else if (direction.x < 0.0f) { - xDistance = (floors.x - entry.x) / direction.x; - } - - // and the distance to the next z coordinate - float zDistance = FLT_MAX; - if (direction.z > 0.0f) { - zDistance = (ceils.z - entry.z) / direction.z; - } else if (direction.z < 0.0f) { - zDistance = (floors.z - entry.z) / direction.z; - } - - // the exit distance is the lower of those two - float exitDistance = qMin(xDistance, zDistance); - glm::vec3 exit, nextFloors = floors, nextCeils = ceils; - if (exitDistance == FLT_MAX) { - withinBounds = false; // line points upwards/downwards; check this cell only - - } else { - // find the exit point and the next cell, and determine whether it's still within the bounds - exit = entry + exitDistance * direction; - withinBounds = (exit.y >= 0.0f && exit.y <= highestY); - if (exitDistance == xDistance) { - if (direction.x > 0.0f) { - nextFloors.x += 1.0f; - withinBounds &= (nextCeils.x += 1.0f) <= highestX; - } else { - withinBounds &= (nextFloors.x -= 1.0f) >= 0.0f; - nextCeils.x -= 1.0f; - } - } - if (exitDistance == zDistance) { - if (direction.z > 0.0f) { - nextFloors.z += 1.0f; - withinBounds &= (nextCeils.z += 1.0f) <= highestZ; - } else { - withinBounds &= (nextFloors.z -= 1.0f) >= 0.0f; - nextCeils.z -= 1.0f; - } - } - // check the vertical range of the ray against the ranges of the cell heights - if (qMin(entry.y, exit.y) > qMax(qMax(upperLeft, upperRight), qMax(lowerLeft, lowerRight)) || - qMax(entry.y, exit.y) < qMin(qMin(upperLeft, upperRight), qMin(lowerLeft, lowerRight))) { - entry = exit; - floors = nextFloors; - ceils = nextCeils; - accumulatedDistance += exitDistance; - continue; - } - } - // having passed the bounds check, we must check against the planes - glm::vec3 relativeEntry = entry - glm::vec3(floors.x, upperLeft, floors.z); - - // first check the triangle including the Z+ segment - glm::vec3 lowerNormal(lowerLeft - lowerRight, 1.0f, upperLeft - lowerLeft); - float lowerProduct = glm::dot(lowerNormal, direction); - if (lowerProduct != 0.0f) { - float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct; - glm::vec3 intersection = relativeEntry + planeDistance * direction; - if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && - intersection.z >= intersection.x) { - distance = rayDistance + (accumulatedDistance + planeDistance) * _increment; - normal = glm::normalize(lowerNormal); - return true; - } - } - - // then the one with the X+ segment - glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight); - float upperProduct = glm::dot(upperNormal, direction); - if (upperProduct != 0.0f) { - float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct; - glm::vec3 intersection = relativeEntry + planeDistance * direction; - if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && - intersection.x >= intersection.z) { - distance = rayDistance + (accumulatedDistance + planeDistance) * _increment; - normal = glm::normalize(upperNormal); - return true; - } - } - - // no joy; continue on our way - entry = exit; - floors = nextFloors; - ceils = nextCeils; - accumulatedDistance += exitDistance; - } - - return false; -} diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 5033d116b0..b80da9b2a0 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -28,9 +28,7 @@ class MetavoxelNode; class MetavoxelRendererImplementation; class MetavoxelVisitation; class MetavoxelVisitor; -class NetworkValue; class Spanner; -class SpannerRenderer; /// Determines whether to subdivide each node when traversing. Contains the position (presumed to be of the viewer) and a /// threshold value, where lower thresholds cause smaller/more distant voxels to be subdivided. @@ -526,281 +524,4 @@ public: virtual QByteArray getImplementationClassName() const; }; -/// An object that spans multiple octree cells. -class Spanner : public SharedObject { - Q_OBJECT - Q_PROPERTY(Box bounds MEMBER _bounds WRITE setBounds NOTIFY boundsChanged DESIGNABLE false) - Q_PROPERTY(float placementGranularity MEMBER _placementGranularity DESIGNABLE false) - Q_PROPERTY(float voxelizationGranularity MEMBER _voxelizationGranularity DESIGNABLE false) - -public: - - /// Returns the value of the global visit counter and increments it. - static int getAndIncrementNextVisit() { return _nextVisit.fetchAndAddOrdered(1); } - - Spanner(); - - void setBounds(const Box& bounds); - const Box& getBounds() const { return _bounds; } - - void setPlacementGranularity(float granularity) { _placementGranularity = granularity; } - float getPlacementGranularity() const { return _placementGranularity; } - - void setVoxelizationGranularity(float granularity) { _voxelizationGranularity = granularity; } - float getVoxelizationGranularity() const { return _voxelizationGranularity; } - - void setMerged(bool merged) { _merged = merged; } - bool isMerged() const { return _merged; } - - /// Checks whether we've visited this object on the current traversal. If we have, returns false. - /// If we haven't, sets the last visit identifier and returns true. - bool testAndSetVisited(int visit); - - /// Returns a pointer to the renderer, creating it if necessary. - SpannerRenderer* getRenderer(); - - /// Finds the intersection between the described ray and this spanner. - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - - /// Checks whether this spanner has its own colors. - virtual bool hasOwnColors() const; - - /// Checks whether this spanner has its own materials. - virtual bool hasOwnMaterials() const; - - /// Checks whether the spanner contains the specified point. - virtual bool contains(const glm::vec3& point); - - /// Retrieves the color at the specified point. - virtual QRgb getColorAt(const glm::vec3& point); - - /// Retrieves the material at the specified point. - virtual int getMaterialAt(const glm::vec3& point); - - /// Retrieves a reference to the list of materials. - virtual QVector& getMaterials(); - - /// Finds the intersection, if any, between the specified line segment and the spanner. - virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); - -signals: - - void boundsWillChange(); - void boundsChanged(const Box& bounds); - -protected: - - SpannerRenderer* _renderer; - - /// Returns the name of the class to instantiate in order to render this spanner. - virtual QByteArray getRendererClassName() const; - -private: - - Box _bounds; - float _placementGranularity; - float _voxelizationGranularity; - bool _merged; - QHash _lastVisits; ///< last visit identifiers for each thread - QMutex _lastVisitsMutex; - - static QAtomicInt _nextVisit; ///< the global visit counter -}; - -/// Base class for objects that can render spanners. -class SpannerRenderer : public QObject { - Q_OBJECT - -public: - - enum Mode { DEFAULT_MODE, DIFFUSE_MODE, NORMAL_MODE }; - - Q_INVOKABLE SpannerRenderer(); - - virtual void init(Spanner* spanner); - virtual void simulate(float deltaTime); - virtual void render(const glm::vec4& color, Mode mode); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - -protected: - - Spanner* _spanner; -}; - -/// An object with a 3D transform. -class Transformable : public Spanner { - Q_OBJECT - Q_PROPERTY(glm::vec3 translation MEMBER _translation WRITE setTranslation NOTIFY translationChanged) - Q_PROPERTY(glm::quat rotation MEMBER _rotation WRITE setRotation NOTIFY rotationChanged) - Q_PROPERTY(float scale MEMBER _scale WRITE setScale NOTIFY scaleChanged) - -public: - - Transformable(); - - void setTranslation(const glm::vec3& translation); - const glm::vec3& getTranslation() const { return _translation; } - - void setRotation(const glm::quat& rotation); - const glm::quat& getRotation() const { return _rotation; } - - void setScale(float scale); - float getScale() const { return _scale; } - -signals: - - void translationChanged(const glm::vec3& translation); - void rotationChanged(const glm::quat& rotation); - void scaleChanged(float scale); - -private: - - glm::vec3 _translation; - glm::quat _rotation; - float _scale; -}; - -/// A transformable object with a color. -class ColorTransformable : public Transformable { - Q_OBJECT - Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged DESIGNABLE false) - -public: - - ColorTransformable(); - - void setColor(const QColor& color); - const QColor& getColor() const { return _color; } - -signals: - - void colorChanged(const QColor& color); - -protected: - - QColor _color; -}; - -/// A sphere. -class Sphere : public ColorTransformable { - Q_OBJECT - -public: - - Q_INVOKABLE Sphere(); - - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - virtual bool contains(const glm::vec3& point); - virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); - -protected: - - virtual QByteArray getRendererClassName() const; - -private slots: - - void updateBounds(); -}; - -/// A cuboid. -class Cuboid : public ColorTransformable { - Q_OBJECT - Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged) - Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged) - -public: - - Q_INVOKABLE Cuboid(); - - void setAspectY(float aspectY); - float getAspectY() const { return _aspectY; } - - void setAspectZ(float aspectZ); - float getAspectZ() const { return _aspectZ; } - - virtual bool contains(const glm::vec3& point); - virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); - -signals: - - void aspectYChanged(float aspectY); - void aspectZChanged(float aspectZ); - -protected: - - virtual QByteArray getRendererClassName() const; - -private slots: - - void updateBoundsAndPlanes(); - -private: - - float _aspectY; - float _aspectZ; - - static const int PLANE_COUNT = 6; - glm::vec4 _planes[PLANE_COUNT]; -}; - -/// A static 3D model loaded from the network. -class StaticModel : public Transformable { - Q_OBJECT - Q_PROPERTY(QUrl url MEMBER _url WRITE setURL NOTIFY urlChanged) - -public: - - Q_INVOKABLE StaticModel(); - - void setURL(const QUrl& url); - const QUrl& getURL() const { return _url; } - - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - -signals: - - void urlChanged(const QUrl& url); - -protected: - - virtual QByteArray getRendererClassName() const; - -private: - - QUrl _url; -}; - -/// A heightfield represented as a spanner. -class Heightfield : public Transformable { - Q_OBJECT - -public: - - Heightfield(const Box& bounds, float increment, const QByteArray& height, const QByteArray& color, - const QByteArray& material, const QVector& materials); - - QByteArray& getHeight() { return _height; } - QByteArray& getColor() { return _color; } - QByteArray& getMaterial() { return _material; } - - virtual bool hasOwnColors() const; - virtual bool hasOwnMaterials() const; - virtual QRgb getColorAt(const glm::vec3& point); - virtual int getMaterialAt(const glm::vec3& point); - virtual QVector& getMaterials(); - - virtual bool contains(const glm::vec3& point); - virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); - -private: - - float _increment; - int _width; - float _heightScale; - QByteArray _height; - QByteArray _color; - QByteArray _material; - QVector _materials; -}; - #endif // hifi_MetavoxelData_h diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 64e7863dee..d40b49a2e6 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -10,6 +10,7 @@ // #include "MetavoxelMessages.h" +#include "Spanner.h" void MetavoxelEditMessage::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { static_cast(edit.data())->apply(data, objects); @@ -754,7 +755,7 @@ int HeightfieldClearFetchVisitor::visit(MetavoxelInfo& info) { } // create spanner if necessary - Heightfield* spanner = static_cast(_spanner.data()); + TempHeightfield* spanner = static_cast(_spanner.data()); float increment = 1.0f / heightScale; if (!spanner) { _spannerBounds.minimum = glm::floor(_bounds.minimum / increment) * increment; @@ -767,7 +768,7 @@ int HeightfieldClearFetchVisitor::visit(MetavoxelInfo& info) { Box innerBounds = _spannerBounds; innerBounds.maximum.x -= increment; innerBounds.maximum.z -= increment; - _spanner = spanner = new Heightfield(innerBounds, increment, QByteArray(heightfieldArea, 0), + _spanner = spanner = new TempHeightfield(innerBounds, increment, QByteArray(heightfieldArea, 0), QByteArray(heightfieldArea * DataBlock::COLOR_BYTES, 0), QByteArray(heightfieldArea, 0), QVector()); } diff --git a/libraries/metavoxels/src/Spanner.cpp b/libraries/metavoxels/src/Spanner.cpp new file mode 100644 index 0000000000..c6650ec33c --- /dev/null +++ b/libraries/metavoxels/src/Spanner.cpp @@ -0,0 +1,783 @@ +// +// 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 "Spanner.h" + +REGISTER_META_OBJECT(Spanner) +REGISTER_META_OBJECT(Sphere) +REGISTER_META_OBJECT(Cuboid) +REGISTER_META_OBJECT(StaticModel) +REGISTER_META_OBJECT(Heightfield) + +static int heightfieldHeightTypeId = registerSimpleMetaType(); +static int heightfieldColorTypeId = registerSimpleMetaType(); +static int heightfieldMaterialTypeId = registerSimpleMetaType(); + +const float DEFAULT_PLACEMENT_GRANULARITY = 0.01f; +const float DEFAULT_VOXELIZATION_GRANULARITY = powf(2.0f, -3.0f); + +Spanner::Spanner() : + _renderer(NULL), + _placementGranularity(DEFAULT_PLACEMENT_GRANULARITY), + _voxelizationGranularity(DEFAULT_VOXELIZATION_GRANULARITY), + _merged(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::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { + return _bounds.findRayIntersection(origin, direction, distance); +} + +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 glm::vec4& color, Mode mode) { + // 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"; +} + +const float EIGHT_BIT_MAXIMUM = 255.0f; + +TempHeightfield::TempHeightfield(const Box& bounds, float increment, const QByteArray& height, const QByteArray& color, + const QByteArray& material, const QVector& materials) : + _increment(increment), + _width((int)glm::round((bounds.maximum.x - bounds.minimum.x) / increment) + 1), + _heightScale((bounds.maximum.y - bounds.minimum.y) / EIGHT_BIT_MAXIMUM), + _height(height), + _color(color), + _material(material), + _materials(materials) { + + setBounds(bounds); +} + +bool TempHeightfield::hasOwnColors() const { + return true; +} + +bool TempHeightfield::hasOwnMaterials() const { + return true; +} + +QRgb TempHeightfield::getColorAt(const glm::vec3& point) { + glm::vec3 relative = (point - getBounds().minimum) / _increment; + glm::vec3 floors = glm::floor(relative); + glm::vec3 ceils = glm::ceil(relative); + glm::vec3 fracts = glm::fract(relative); + int floorX = (int)floors.x; + int floorZ = (int)floors.z; + int ceilX = (int)ceils.x; + int ceilZ = (int)ceils.z; + const uchar* src = (const uchar*)_color.constData(); + const uchar* upperLeft = src + (floorZ * _width + floorX) * DataBlock::COLOR_BYTES; + const uchar* lowerRight = src + (ceilZ * _width + ceilX) * DataBlock::COLOR_BYTES; + glm::vec3 interpolatedColor = glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), + glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z); + + // the final vertex (and thus which triangle we check) depends on which half we're on + if (fracts.x >= fracts.z) { + const uchar* upperRight = src + (floorZ * _width + ceilX) * DataBlock::COLOR_BYTES; + interpolatedColor = glm::mix(interpolatedColor, glm::mix(glm::vec3(upperRight[0], upperRight[1], upperRight[2]), + glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z), (fracts.x - fracts.z) / (1.0f - fracts.z)); + + } else { + const uchar* lowerLeft = src + (ceilZ * _width + floorX) * DataBlock::COLOR_BYTES; + interpolatedColor = glm::mix(glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), + glm::vec3(lowerLeft[0], lowerLeft[1], lowerLeft[2]), fracts.z), interpolatedColor, fracts.x / fracts.z); + } + return qRgb(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b); +} + +int TempHeightfield::getMaterialAt(const glm::vec3& point) { + glm::vec3 relative = (point - getBounds().minimum) / _increment; + const uchar* src = (const uchar*)_material.constData(); + return src[(int)glm::round(relative.z) * _width + (int)glm::round(relative.x)]; +} + +QVector& TempHeightfield::getMaterials() { + return _materials; +} + +bool TempHeightfield::contains(const glm::vec3& point) { + if (!getBounds().contains(point)) { + return false; + } + glm::vec3 relative = (point - getBounds().minimum) / _increment; + glm::vec3 floors = glm::floor(relative); + glm::vec3 ceils = glm::ceil(relative); + glm::vec3 fracts = glm::fract(relative); + int floorX = (int)floors.x; + int floorZ = (int)floors.z; + int ceilX = (int)ceils.x; + int ceilZ = (int)ceils.z; + const uchar* src = (const uchar*)_height.constData(); + float upperLeft = src[floorZ * _width + floorX]; + float lowerRight = src[ceilZ * _width + ceilX]; + float interpolatedHeight = glm::mix(upperLeft, lowerRight, fracts.z); + + // the final vertex (and thus which triangle we check) depends on which half we're on + if (fracts.x >= fracts.z) { + float upperRight = src[floorZ * _width + ceilX]; + interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z), + (fracts.x - fracts.z) / (1.0f - fracts.z)); + + } else { + float lowerLeft = src[ceilZ * _width + floorX]; + interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, fracts.x / fracts.z); + } + return interpolatedHeight != 0.0f && point.y <= interpolatedHeight * _heightScale + getBounds().minimum.y; +} + +bool TempHeightfield::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) { + // find the initial location in heightfield coordinates + float rayDistance; + glm::vec3 direction = end - start; + if (!getBounds().findRayIntersection(start, direction, rayDistance) || rayDistance > 1.0f) { + return false; + } + glm::vec3 entry = start + direction * rayDistance; + const float DISTANCE_THRESHOLD = 0.001f; + if (glm::abs(entry.x - getBounds().minimum.x) < DISTANCE_THRESHOLD) { + normal = glm::vec3(-1.0f, 0.0f, 0.0f); + distance = rayDistance; + return true; + + } else if (glm::abs(entry.x - getBounds().maximum.x) < DISTANCE_THRESHOLD) { + normal = glm::vec3(1.0f, 0.0f, 0.0f); + distance = rayDistance; + return true; + + } else if (glm::abs(entry.y - getBounds().minimum.y) < DISTANCE_THRESHOLD) { + normal = glm::vec3(0.0f, -1.0f, 0.0f); + distance = rayDistance; + return true; + + } else if (glm::abs(entry.y - getBounds().maximum.y) < DISTANCE_THRESHOLD) { + normal = glm::vec3(0.0f, 1.0f, 0.0f); + distance = rayDistance; + return true; + + } else if (glm::abs(entry.z - getBounds().minimum.z) < DISTANCE_THRESHOLD) { + normal = glm::vec3(0.0f, 0.0f, -1.0f); + distance = rayDistance; + return true; + + } else if (glm::abs(entry.z - getBounds().maximum.z) < DISTANCE_THRESHOLD) { + normal = glm::vec3(0.0f, 0.0f, 1.0f); + distance = rayDistance; + return true; + } + entry = (entry - getBounds().minimum) / _increment; + glm::vec3 floors = glm::floor(entry); + glm::vec3 ceils = glm::ceil(entry); + if (floors.x == ceils.x) { + if (direction.x > 0.0f) { + ceils.x += 1.0f; + } else { + floors.x -= 1.0f; + } + } + if (floors.z == ceils.z) { + if (direction.z > 0.0f) { + ceils.z += 1.0f; + } else { + floors.z -= 1.0f; + } + } + + bool withinBounds = true; + float accumulatedDistance = 0.0f; + const uchar* src = (const uchar*)_height.constData(); + int highestX = _width - 1; + float highestY = (getBounds().maximum.y - getBounds().minimum.y) / _increment; + int highestZ = (int)glm::round((getBounds().maximum.z - getBounds().minimum.z) / _increment); + float heightScale = _heightScale / _increment; + while (withinBounds && accumulatedDistance <= 1.0f) { + // find the heights at the corners of the current cell + int floorX = qMin(qMax((int)floors.x, 0), highestX); + int floorZ = qMin(qMax((int)floors.z, 0), highestZ); + int ceilX = qMin(qMax((int)ceils.x, 0), highestX); + int ceilZ = qMin(qMax((int)ceils.z, 0), highestZ); + float upperLeft = src[floorZ * _width + floorX] * heightScale; + float upperRight = src[floorZ * _width + ceilX] * heightScale; + float lowerLeft = src[ceilZ * _width + floorX] * heightScale; + float lowerRight = src[ceilZ * _width + ceilX] * heightScale; + + // find the distance to the next x coordinate + float xDistance = FLT_MAX; + if (direction.x > 0.0f) { + xDistance = (ceils.x - entry.x) / direction.x; + } else if (direction.x < 0.0f) { + xDistance = (floors.x - entry.x) / direction.x; + } + + // and the distance to the next z coordinate + float zDistance = FLT_MAX; + if (direction.z > 0.0f) { + zDistance = (ceils.z - entry.z) / direction.z; + } else if (direction.z < 0.0f) { + zDistance = (floors.z - entry.z) / direction.z; + } + + // the exit distance is the lower of those two + float exitDistance = qMin(xDistance, zDistance); + glm::vec3 exit, nextFloors = floors, nextCeils = ceils; + if (exitDistance == FLT_MAX) { + withinBounds = false; // line points upwards/downwards; check this cell only + + } else { + // find the exit point and the next cell, and determine whether it's still within the bounds + exit = entry + exitDistance * direction; + withinBounds = (exit.y >= 0.0f && exit.y <= highestY); + if (exitDistance == xDistance) { + if (direction.x > 0.0f) { + nextFloors.x += 1.0f; + withinBounds &= (nextCeils.x += 1.0f) <= highestX; + } else { + withinBounds &= (nextFloors.x -= 1.0f) >= 0.0f; + nextCeils.x -= 1.0f; + } + } + if (exitDistance == zDistance) { + if (direction.z > 0.0f) { + nextFloors.z += 1.0f; + withinBounds &= (nextCeils.z += 1.0f) <= highestZ; + } else { + withinBounds &= (nextFloors.z -= 1.0f) >= 0.0f; + nextCeils.z -= 1.0f; + } + } + // check the vertical range of the ray against the ranges of the cell heights + if (qMin(entry.y, exit.y) > qMax(qMax(upperLeft, upperRight), qMax(lowerLeft, lowerRight)) || + qMax(entry.y, exit.y) < qMin(qMin(upperLeft, upperRight), qMin(lowerLeft, lowerRight))) { + entry = exit; + floors = nextFloors; + ceils = nextCeils; + accumulatedDistance += exitDistance; + continue; + } + } + // having passed the bounds check, we must check against the planes + glm::vec3 relativeEntry = entry - glm::vec3(floors.x, upperLeft, floors.z); + + // first check the triangle including the Z+ segment + glm::vec3 lowerNormal(lowerLeft - lowerRight, 1.0f, upperLeft - lowerLeft); + float lowerProduct = glm::dot(lowerNormal, direction); + if (lowerProduct != 0.0f) { + float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct; + glm::vec3 intersection = relativeEntry + planeDistance * direction; + if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && + intersection.z >= intersection.x) { + distance = rayDistance + (accumulatedDistance + planeDistance) * _increment; + normal = glm::normalize(lowerNormal); + return true; + } + } + + // then the one with the X+ segment + glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight); + float upperProduct = glm::dot(upperNormal, direction); + if (upperProduct != 0.0f) { + float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct; + glm::vec3 intersection = relativeEntry + planeDistance * direction; + if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && + intersection.x >= intersection.z) { + distance = rayDistance + (accumulatedDistance + planeDistance) * _increment; + normal = glm::normalize(upperNormal); + return true; + } + } + + // no joy; continue on our way + entry = exit; + floors = nextFloors; + ceils = nextCeils; + accumulatedDistance += exitDistance; + } + + return false; +} + +HeightfieldData::HeightfieldData(int width) : + _width(width) { +} + +HeightfieldHeight::HeightfieldHeight(int width, const QVector& contents) : + HeightfieldData(width), + _contents(contents) { +} + +HeightfieldHeight::HeightfieldHeight(Bitstream& in, int bytes) { +} + +HeightfieldHeight::HeightfieldHeight(Bitstream& in, int bytes, const HeightfieldHeightPointer& reference) { +} + +void HeightfieldHeight::write(Bitstream& out) { + QMutexLocker locker(&_encodedMutex); + if (_encoded.isEmpty()) { + + } + out << _encoded.size(); + out.writeAligned(_encoded); +} + +void HeightfieldHeight::writeDelta(Bitstream& out, const HeightfieldHeightPointer& reference) { +} + +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(0, QVector()); + } + return in; +} + +template<> void Bitstream::writeRawDelta(const HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference) { +} + +template<> void Bitstream::readRawDelta(HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference) { +} + +HeightfieldColor::HeightfieldColor(int width, const QByteArray& contents) : + HeightfieldData(width), + _contents(contents) { +} + +HeightfieldColor::HeightfieldColor(Bitstream& in, int bytes) { +} + +HeightfieldColor::HeightfieldColor(Bitstream& in, int bytes, const HeightfieldColorPointer& reference) { +} + +void HeightfieldColor::write(Bitstream& out) { + QMutexLocker locker(&_encodedMutex); + if (_encoded.isEmpty()) { + + } + out << _encoded.size(); + out.writeAligned(_encoded); +} + +void HeightfieldColor::writeDelta(Bitstream& out, const HeightfieldColorPointer& reference) { +} + +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(0, QByteArray()); + } + return in; +} + +template<> void Bitstream::writeRawDelta(const HeightfieldColorPointer& value, const HeightfieldColorPointer& reference) { + +} + +template<> void Bitstream::readRawDelta(HeightfieldColorPointer& value, const HeightfieldColorPointer& reference) { +} + +HeightfieldMaterial::HeightfieldMaterial(int width, const QByteArray& contents, + const QVector& materials) : + HeightfieldData(width), + _contents(contents), + _materials(materials) { +} + +HeightfieldMaterial::HeightfieldMaterial(Bitstream& in, int bytes) { +} + +HeightfieldMaterial::HeightfieldMaterial(Bitstream& in, int bytes, const HeightfieldMaterialPointer& reference) { +} + +void HeightfieldMaterial::write(Bitstream& out) { + QMutexLocker locker(&_encodedMutex); + if (_encoded.isEmpty()) { + + } + out << _encoded.size(); + out.writeAligned(_encoded); + out << _materials; +} + +void HeightfieldMaterial::writeDelta(Bitstream& out, const HeightfieldMaterialPointer& reference) { +} + +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(0, QByteArray(), QVector()); + } + return in; +} + +template<> void Bitstream::writeRawDelta(const HeightfieldMaterialPointer& value, + const HeightfieldMaterialPointer& reference) { +} + +template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference) { +} + +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(); +} + +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); + } +} + +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(-extent, extent)); +} diff --git a/libraries/metavoxels/src/Spanner.h b/libraries/metavoxels/src/Spanner.h new file mode 100644 index 0000000000..e8ef2f30c1 --- /dev/null +++ b/libraries/metavoxels/src/Spanner.h @@ -0,0 +1,454 @@ +// +// Spanner.h +// 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 +// + +#ifndef hifi_Spanner_h +#define hifi_Spanner_h + +#include + +#include "AttributeRegistry.h" +#include "MetavoxelUtil.h" + +class HeightfieldColor; +class HeightfieldHeight; +class HeightfieldMaterial; +class SpannerRenderer; + +/// An object that spans multiple octree cells. +class Spanner : public SharedObject { + Q_OBJECT + Q_PROPERTY(Box bounds MEMBER _bounds WRITE setBounds NOTIFY boundsChanged DESIGNABLE false) + Q_PROPERTY(float placementGranularity MEMBER _placementGranularity DESIGNABLE false) + Q_PROPERTY(float voxelizationGranularity MEMBER _voxelizationGranularity DESIGNABLE false) + +public: + + /// Returns the value of the global visit counter and increments it. + static int getAndIncrementNextVisit() { return _nextVisit.fetchAndAddOrdered(1); } + + Spanner(); + + void setBounds(const Box& bounds); + const Box& getBounds() const { return _bounds; } + + void setPlacementGranularity(float granularity) { _placementGranularity = granularity; } + float getPlacementGranularity() const { return _placementGranularity; } + + void setVoxelizationGranularity(float granularity) { _voxelizationGranularity = granularity; } + float getVoxelizationGranularity() const { return _voxelizationGranularity; } + + void setMerged(bool merged) { _merged = merged; } + bool isMerged() const { return _merged; } + + /// Checks whether we've visited this object on the current traversal. If we have, returns false. + /// If we haven't, sets the last visit identifier and returns true. + bool testAndSetVisited(int visit); + + /// Returns a pointer to the renderer, creating it if necessary. + SpannerRenderer* getRenderer(); + + /// Finds the intersection between the described ray and this spanner. + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + + /// Checks whether this spanner has its own colors. + virtual bool hasOwnColors() const; + + /// Checks whether this spanner has its own materials. + virtual bool hasOwnMaterials() const; + + /// Checks whether the spanner contains the specified point. + virtual bool contains(const glm::vec3& point); + + /// Retrieves the color at the specified point. + virtual QRgb getColorAt(const glm::vec3& point); + + /// Retrieves the material at the specified point. + virtual int getMaterialAt(const glm::vec3& point); + + /// Retrieves a reference to the list of materials. + virtual QVector& getMaterials(); + + /// Finds the intersection, if any, between the specified line segment and the spanner. + virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + +signals: + + void boundsWillChange(); + void boundsChanged(const Box& bounds); + +protected: + + SpannerRenderer* _renderer; + + /// Returns the name of the class to instantiate in order to render this spanner. + virtual QByteArray getRendererClassName() const; + +private: + + Box _bounds; + float _placementGranularity; + float _voxelizationGranularity; + bool _merged; + QHash _lastVisits; ///< last visit identifiers for each thread + QMutex _lastVisitsMutex; + + static QAtomicInt _nextVisit; ///< the global visit counter +}; + +/// Base class for objects that can render spanners. +class SpannerRenderer : public QObject { + Q_OBJECT + +public: + + enum Mode { DEFAULT_MODE, DIFFUSE_MODE, NORMAL_MODE }; + + Q_INVOKABLE SpannerRenderer(); + + virtual void init(Spanner* spanner); + virtual void simulate(float deltaTime); + virtual void render(const glm::vec4& color, Mode mode); + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + +protected: + + Spanner* _spanner; +}; + +/// An object with a 3D transform. +class Transformable : public Spanner { + Q_OBJECT + Q_PROPERTY(glm::vec3 translation MEMBER _translation WRITE setTranslation NOTIFY translationChanged) + Q_PROPERTY(glm::quat rotation MEMBER _rotation WRITE setRotation NOTIFY rotationChanged) + Q_PROPERTY(float scale MEMBER _scale WRITE setScale NOTIFY scaleChanged) + +public: + + Transformable(); + + void setTranslation(const glm::vec3& translation); + const glm::vec3& getTranslation() const { return _translation; } + + void setRotation(const glm::quat& rotation); + const glm::quat& getRotation() const { return _rotation; } + + void setScale(float scale); + float getScale() const { return _scale; } + +signals: + + void translationChanged(const glm::vec3& translation); + void rotationChanged(const glm::quat& rotation); + void scaleChanged(float scale); + +private: + + glm::vec3 _translation; + glm::quat _rotation; + float _scale; +}; + +/// A transformable object with a color. +class ColorTransformable : public Transformable { + Q_OBJECT + Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged DESIGNABLE false) + +public: + + ColorTransformable(); + + void setColor(const QColor& color); + const QColor& getColor() const { return _color; } + +signals: + + void colorChanged(const QColor& color); + +protected: + + QColor _color; +}; + +/// A sphere. +class Sphere : public ColorTransformable { + Q_OBJECT + +public: + + Q_INVOKABLE Sphere(); + + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + virtual bool contains(const glm::vec3& point); + virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + +protected: + + virtual QByteArray getRendererClassName() const; + +private slots: + + void updateBounds(); +}; + +/// A cuboid. +class Cuboid : public ColorTransformable { + Q_OBJECT + Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged) + Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged) + +public: + + Q_INVOKABLE Cuboid(); + + void setAspectY(float aspectY); + float getAspectY() const { return _aspectY; } + + void setAspectZ(float aspectZ); + float getAspectZ() const { return _aspectZ; } + + virtual bool contains(const glm::vec3& point); + virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + +signals: + + void aspectYChanged(float aspectY); + void aspectZChanged(float aspectZ); + +protected: + + virtual QByteArray getRendererClassName() const; + +private slots: + + void updateBoundsAndPlanes(); + +private: + + float _aspectY; + float _aspectZ; + + static const int PLANE_COUNT = 6; + glm::vec4 _planes[PLANE_COUNT]; +}; + +/// A static 3D model loaded from the network. +class StaticModel : public Transformable { + Q_OBJECT + Q_PROPERTY(QUrl url MEMBER _url WRITE setURL NOTIFY urlChanged) + +public: + + Q_INVOKABLE StaticModel(); + + void setURL(const QUrl& url); + const QUrl& getURL() const { return _url; } + + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + +signals: + + void urlChanged(const QUrl& url); + +protected: + + virtual QByteArray getRendererClassName() const; + +private: + + QUrl _url; +}; + +/// A heightfield represented as a spanner. +class TempHeightfield : public Transformable { + Q_OBJECT + +public: + + TempHeightfield(const Box& bounds, float increment, const QByteArray& height, const QByteArray& color, + const QByteArray& material, const QVector& materials); + + QByteArray& getHeight() { return _height; } + QByteArray& getColor() { return _color; } + QByteArray& getMaterial() { return _material; } + + virtual bool hasOwnColors() const; + virtual bool hasOwnMaterials() const; + virtual QRgb getColorAt(const glm::vec3& point); + virtual int getMaterialAt(const glm::vec3& point); + virtual QVector& getMaterials(); + + virtual bool contains(const glm::vec3& point); + virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + +private: + + float _increment; + int _width; + float _heightScale; + QByteArray _height; + QByteArray _color; + QByteArray _material; + QVector _materials; +}; + +/// Base class for heightfield data blocks. +class HeightfieldData : public DataBlock { +public: + + HeightfieldData(int width = 0); + + int getWidth() const { return _width; } + +protected: + + int _width; +}; + +typedef QExplicitlySharedDataPointer HeightfieldHeightPointer; + +/// A block of height data associated with a heightfield. +class HeightfieldHeight : public HeightfieldData { +public: + + HeightfieldHeight(int width, const QVector& contents); + HeightfieldHeight(Bitstream& in, int bytes); + HeightfieldHeight(Bitstream& in, int bytes, const HeightfieldHeightPointer& reference); + + const QVector& getContents() const { return _contents; } + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldHeightPointer& reference); + +private: + + QVector _contents; +}; + +Q_DECLARE_METATYPE(HeightfieldHeightPointer) + +Bitstream& operator<<(Bitstream& out, const HeightfieldHeightPointer& value); +Bitstream& operator>>(Bitstream& in, HeightfieldHeightPointer& value); + +template<> void Bitstream::writeRawDelta(const HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference); +template<> void Bitstream::readRawDelta(HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference); + +typedef QExplicitlySharedDataPointer HeightfieldColorPointer; + +/// A block of color data associated with a heightfield. +class HeightfieldColor : public HeightfieldData { +public: + + HeightfieldColor(int width, const QByteArray& contents); + HeightfieldColor(Bitstream& in, int bytes); + HeightfieldColor(Bitstream& in, int bytes, const HeightfieldColorPointer& reference); + + const QByteArray& getContents() const { return _contents; } + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldColorPointer& reference); + +private: + + QByteArray _contents; +}; + +Q_DECLARE_METATYPE(HeightfieldColorPointer) + +Bitstream& operator<<(Bitstream& out, const HeightfieldColorPointer& value); +Bitstream& operator>>(Bitstream& in, HeightfieldColorPointer& value); + +template<> void Bitstream::writeRawDelta(const HeightfieldColorPointer& value, const HeightfieldColorPointer& reference); +template<> void Bitstream::readRawDelta(HeightfieldColorPointer& value, const HeightfieldColorPointer& reference); + +typedef QExplicitlySharedDataPointer HeightfieldMaterialPointer; + +/// A block of material data associated with a heightfield. +class HeightfieldMaterial : public HeightfieldData { +public: + + HeightfieldMaterial(int width, const QByteArray& contents, const QVector& materials); + HeightfieldMaterial(Bitstream& in, int bytes); + HeightfieldMaterial(Bitstream& in, int bytes, const HeightfieldMaterialPointer& reference); + + const QByteArray& getContents() const { return _contents; } + const QVector& getMaterials() const { return _materials; } + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldMaterialPointer& reference); + +private: + + QByteArray _contents; + QVector _materials; +}; + +Q_DECLARE_METATYPE(HeightfieldMaterialPointer) + +Bitstream& operator<<(Bitstream& out, const HeightfieldMaterialPointer& value); +Bitstream& operator>>(Bitstream& in, HeightfieldMaterialPointer& value); + +template<> void Bitstream::writeRawDelta(const HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference); +template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference); + +/// A heightfield represented as a spanner. +class Heightfield : public Transformable { + Q_OBJECT + Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged) + Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged) + Q_PROPERTY(HeightfieldHeightPointer height MEMBER _height WRITE setHeight NOTIFY heightChanged) + Q_PROPERTY(HeightfieldColorPointer color MEMBER _color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(HeightfieldMaterialPointer material MEMBER _material WRITE setMaterial NOTIFY materialChanged) + +public: + + Q_INVOKABLE Heightfield(); + + void setAspectY(float aspectY); + float getAspectY() const { return _aspectY; } + + void setAspectZ(float aspectZ); + float getAspectZ() const { return _aspectZ; } + + void setHeight(const HeightfieldHeightPointer& height); + const HeightfieldHeightPointer& getHeight() const { return _height; } + + void setColor(const HeightfieldColorPointer& color); + const HeightfieldColorPointer& getColor() const { return _color; } + + void setMaterial(const HeightfieldMaterialPointer& material); + const HeightfieldMaterialPointer& getMaterial() const { return _material; } + +signals: + + void aspectYChanged(float aspectY); + void aspectZChanged(float aspectZ); + void heightChanged(const HeightfieldHeightPointer& height); + void colorChanged(const HeightfieldColorPointer& color); + void materialChanged(const HeightfieldMaterialPointer& material); + +protected: + + virtual QByteArray getRendererClassName() const; + +private slots: + + void updateBounds(); + +private: + + float _aspectY; + float _aspectZ; + HeightfieldHeightPointer _height; + HeightfieldColorPointer _color; + HeightfieldMaterialPointer _material; +}; + +#endif // hifi_Spanner_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index b5cf84ee28..8a274ae444 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -81,7 +81,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 8; + return 9; case PacketTypeVoxelData: return VERSION_VOXELS_HAS_FILE_BREAKS; default: