Moved spanners into separate file, working on heightfields as spanners.

This commit is contained in:
Andrzej Kapolka 2014-11-10 15:51:41 -08:00
parent a77525209d
commit 0664ff3181
10 changed files with 1264 additions and 838 deletions

View file

@ -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<Sphere*>(_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) {
}

View file

@ -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

View file

@ -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);

View file

@ -16,6 +16,7 @@
#include "DatagramSequencer.h"
#include "MetavoxelData.h"
#include "Spanner.h"
class PacketRecord;

View file

@ -11,24 +11,15 @@
#include <QDateTime>
#include <QDebugStateSaver>
#include <QThread>
#include <QtDebug>
#include <glm/gtx/transform.hpp>
#include <GeometryUtil.h>
#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<MetavoxelData>();
@ -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<SpannerRenderer*>(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<SharedObjectPointer>& Spanner::getMaterials() {
static QVector<SharedObjectPointer> 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<SharedObjectPointer>& materials) :
_increment(increment),
_width((int)glm::round((bounds.maximum.x - bounds.minimum.x) / increment) + 1),
_heightScale((bounds.maximum.y - bounds.minimum.y) / EIGHT_BIT_MAXIMUM),
_height(height),
_color(color),
_material(material),
_materials(materials) {
setBounds(bounds);
}
bool Heightfield::hasOwnColors() const {
return true;
}
bool Heightfield::hasOwnMaterials() const {
return true;
}
QRgb Heightfield::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<SharedObjectPointer>& Heightfield::getMaterials() {
return _materials;
}
bool Heightfield::contains(const glm::vec3& point) {
if (!getBounds().contains(point)) {
return false;
}
glm::vec3 relative = (point - getBounds().minimum) / _increment;
glm::vec3 floors = glm::floor(relative);
glm::vec3 ceils = glm::ceil(relative);
glm::vec3 fracts = glm::fract(relative);
int floorX = (int)floors.x;
int floorZ = (int)floors.z;
int ceilX = (int)ceils.x;
int ceilZ = (int)ceils.z;
const uchar* src = (const uchar*)_height.constData();
float upperLeft = src[floorZ * _width + floorX];
float lowerRight = src[ceilZ * _width + ceilX];
float interpolatedHeight = glm::mix(upperLeft, lowerRight, fracts.z);
// the final vertex (and thus which triangle we check) depends on which half we're on
if (fracts.x >= fracts.z) {
float upperRight = src[floorZ * _width + ceilX];
interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z),
(fracts.x - fracts.z) / (1.0f - fracts.z));
} else {
float lowerLeft = src[ceilZ * _width + floorX];
interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, fracts.x / fracts.z);
}
return interpolatedHeight != 0.0f && point.y <= interpolatedHeight * _heightScale + getBounds().minimum.y;
}
bool Heightfield::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) {
// find the initial location in heightfield coordinates
float rayDistance;
glm::vec3 direction = end - start;
if (!getBounds().findRayIntersection(start, direction, rayDistance) || rayDistance > 1.0f) {
return false;
}
glm::vec3 entry = start + direction * rayDistance;
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;
}

View file

@ -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<SharedObjectPointer>& getMaterials();
/// Finds the intersection, if any, between the specified line segment and the spanner.
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
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<QThread*, int> _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<SharedObjectPointer>& 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<SharedObjectPointer>& getMaterials();
virtual bool contains(const glm::vec3& point);
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
private:
float _increment;
int _width;
float _heightScale;
QByteArray _height;
QByteArray _color;
QByteArray _material;
QVector<SharedObjectPointer> _materials;
};
#endif // hifi_MetavoxelData_h

View file

@ -10,6 +10,7 @@
//
#include "MetavoxelMessages.h"
#include "Spanner.h"
void MetavoxelEditMessage::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
static_cast<const MetavoxelEdit*>(edit.data())->apply(data, objects);
@ -754,7 +755,7 @@ int HeightfieldClearFetchVisitor::visit(MetavoxelInfo& info) {
}
// create spanner if necessary
Heightfield* spanner = static_cast<Heightfield*>(_spanner.data());
TempHeightfield* spanner = static_cast<TempHeightfield*>(_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<SharedObjectPointer>());
}

View file

@ -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 <QThread>
#include <glm/gtx/transform.hpp>
#include <GeometryUtil.h>
#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<HeightfieldHeightPointer>();
static int heightfieldColorTypeId = registerSimpleMetaType<HeightfieldColorPointer>();
static int heightfieldMaterialTypeId = registerSimpleMetaType<HeightfieldMaterialPointer>();
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<SpannerRenderer*>(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<SharedObjectPointer>& Spanner::getMaterials() {
static QVector<SharedObjectPointer> 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<SharedObjectPointer>& materials) :
_increment(increment),
_width((int)glm::round((bounds.maximum.x - bounds.minimum.x) / increment) + 1),
_heightScale((bounds.maximum.y - bounds.minimum.y) / EIGHT_BIT_MAXIMUM),
_height(height),
_color(color),
_material(material),
_materials(materials) {
setBounds(bounds);
}
bool 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<SharedObjectPointer>& 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<quint16>& 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<quint16>());
}
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<SharedObjectPointer>& 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<SharedObjectPointer>());
}
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));
}

View file

@ -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 <glm/glm.hpp>
#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<SharedObjectPointer>& getMaterials();
/// Finds the intersection, if any, between the specified line segment and the spanner.
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
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<QThread*, int> _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<SharedObjectPointer>& 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<SharedObjectPointer>& getMaterials();
virtual bool contains(const glm::vec3& point);
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
private:
float _increment;
int _width;
float _heightScale;
QByteArray _height;
QByteArray _color;
QByteArray _material;
QVector<SharedObjectPointer> _materials;
};
/// 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<HeightfieldHeight> HeightfieldHeightPointer;
/// A block of height data associated with a heightfield.
class HeightfieldHeight : public HeightfieldData {
public:
HeightfieldHeight(int width, const QVector<quint16>& contents);
HeightfieldHeight(Bitstream& in, int bytes);
HeightfieldHeight(Bitstream& in, int bytes, const HeightfieldHeightPointer& reference);
const QVector<quint16>& getContents() const { return _contents; }
void write(Bitstream& out);
void writeDelta(Bitstream& out, const HeightfieldHeightPointer& reference);
private:
QVector<quint16> _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<HeightfieldColor> 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<HeightfieldMaterial> HeightfieldMaterialPointer;
/// A block of material data associated with a heightfield.
class HeightfieldMaterial : public HeightfieldData {
public:
HeightfieldMaterial(int width, const QByteArray& contents, const QVector<SharedObjectPointer>& materials);
HeightfieldMaterial(Bitstream& in, int bytes);
HeightfieldMaterial(Bitstream& in, int bytes, const HeightfieldMaterialPointer& reference);
const QByteArray& getContents() const { return _contents; }
const QVector<SharedObjectPointer>& getMaterials() const { return _materials; }
void write(Bitstream& out);
void writeDelta(Bitstream& out, const HeightfieldMaterialPointer& reference);
private:
QByteArray _contents;
QVector<SharedObjectPointer> _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

View file

@ -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: