mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 12:57:59 +02:00
Moved spanners into separate file, working on heightfields as spanners.
This commit is contained in:
parent
a77525209d
commit
0664ff3181
10 changed files with 1264 additions and 838 deletions
|
@ -36,6 +36,7 @@ REGISTER_META_OBJECT(DefaultMetavoxelRendererImplementation)
|
||||||
REGISTER_META_OBJECT(SphereRenderer)
|
REGISTER_META_OBJECT(SphereRenderer)
|
||||||
REGISTER_META_OBJECT(CuboidRenderer)
|
REGISTER_META_OBJECT(CuboidRenderer)
|
||||||
REGISTER_META_OBJECT(StaticModelRenderer)
|
REGISTER_META_OBJECT(StaticModelRenderer)
|
||||||
|
REGISTER_META_OBJECT(HeightfieldRenderer)
|
||||||
|
|
||||||
MetavoxelSystem::NetworkSimulation::NetworkSimulation(float dropRate, float repeatRate,
|
MetavoxelSystem::NetworkSimulation::NetworkSimulation(float dropRate, float repeatRate,
|
||||||
int minimumDelay, int maximumDelay, int bandwidthLimit) :
|
int minimumDelay, int maximumDelay, int bandwidthLimit) :
|
||||||
|
@ -2854,6 +2855,7 @@ ProgramObject DefaultMetavoxelRendererImplementation::_voxelCursorProgram;
|
||||||
SphereRenderer::SphereRenderer() {
|
SphereRenderer::SphereRenderer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SphereRenderer::render(const glm::vec4& color, Mode mode) {
|
void SphereRenderer::render(const glm::vec4& color, Mode mode) {
|
||||||
Sphere* sphere = static_cast<Sphere*>(_spanner);
|
Sphere* sphere = static_cast<Sphere*>(_spanner);
|
||||||
const QColor& ownColor = sphere->getColor();
|
const QColor& ownColor = sphere->getColor();
|
||||||
|
@ -2969,3 +2971,9 @@ void StaticModelRenderer::applyScale(float scale) {
|
||||||
void StaticModelRenderer::applyURL(const QUrl& url) {
|
void StaticModelRenderer::applyURL(const QUrl& url) {
|
||||||
_model->setURL(url);
|
_model->setURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HeightfieldRenderer::HeightfieldRenderer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeightfieldRenderer::render(const glm::vec4& color, Mode mode) {
|
||||||
|
}
|
||||||
|
|
|
@ -450,4 +450,15 @@ private:
|
||||||
Model* _model;
|
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
|
#endif // hifi_MetavoxelSystem_h
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include "AttributeRegistry.h"
|
#include "AttributeRegistry.h"
|
||||||
#include "MetavoxelData.h"
|
#include "MetavoxelData.h"
|
||||||
|
#include "Spanner.h"
|
||||||
|
|
||||||
REGISTER_META_OBJECT(FloatAttribute)
|
REGISTER_META_OBJECT(FloatAttribute)
|
||||||
REGISTER_META_OBJECT(MaterialObject)
|
REGISTER_META_OBJECT(MaterialObject)
|
||||||
|
@ -54,6 +55,7 @@ AttributeRegistry::AttributeRegistry() :
|
||||||
// our baseline LOD threshold is for voxels; spanners and heightfields are a different story
|
// our baseline LOD threshold is for voxels; spanners and heightfields are a different story
|
||||||
const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f;
|
const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f;
|
||||||
_spannersAttribute->setLODThresholdMultiplier(SPANNER_LOD_THRESHOLD_MULTIPLIER);
|
_spannersAttribute->setLODThresholdMultiplier(SPANNER_LOD_THRESHOLD_MULTIPLIER);
|
||||||
|
_spannersAttribute->setUserFacing(true);
|
||||||
|
|
||||||
const float HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER = 32.0f;
|
const float HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER = 32.0f;
|
||||||
_heightfieldAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER);
|
_heightfieldAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include "DatagramSequencer.h"
|
#include "DatagramSequencer.h"
|
||||||
#include "MetavoxelData.h"
|
#include "MetavoxelData.h"
|
||||||
|
#include "Spanner.h"
|
||||||
|
|
||||||
class PacketRecord;
|
class PacketRecord;
|
||||||
|
|
||||||
|
|
|
@ -11,24 +11,15 @@
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebugStateSaver>
|
#include <QDebugStateSaver>
|
||||||
#include <QThread>
|
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
|
||||||
#include <glm/gtx/transform.hpp>
|
|
||||||
|
|
||||||
#include <GeometryUtil.h>
|
|
||||||
|
|
||||||
#include "MetavoxelData.h"
|
#include "MetavoxelData.h"
|
||||||
#include "MetavoxelUtil.h"
|
#include "Spanner.h"
|
||||||
|
|
||||||
REGISTER_META_OBJECT(MetavoxelGuide)
|
REGISTER_META_OBJECT(MetavoxelGuide)
|
||||||
REGISTER_META_OBJECT(DefaultMetavoxelGuide)
|
REGISTER_META_OBJECT(DefaultMetavoxelGuide)
|
||||||
REGISTER_META_OBJECT(MetavoxelRenderer)
|
REGISTER_META_OBJECT(MetavoxelRenderer)
|
||||||
REGISTER_META_OBJECT(DefaultMetavoxelRenderer)
|
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>();
|
static int metavoxelDataTypeId = registerSimpleMetaType<MetavoxelData>();
|
||||||
|
|
||||||
|
@ -1800,549 +1791,3 @@ QByteArray DefaultMetavoxelRenderer::getImplementationClassName() const {
|
||||||
return "DefaultMetavoxelRendererImplementation";
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,9 +28,7 @@ class MetavoxelNode;
|
||||||
class MetavoxelRendererImplementation;
|
class MetavoxelRendererImplementation;
|
||||||
class MetavoxelVisitation;
|
class MetavoxelVisitation;
|
||||||
class MetavoxelVisitor;
|
class MetavoxelVisitor;
|
||||||
class NetworkValue;
|
|
||||||
class Spanner;
|
class Spanner;
|
||||||
class SpannerRenderer;
|
|
||||||
|
|
||||||
/// Determines whether to subdivide each node when traversing. Contains the position (presumed to be of the viewer) and a
|
/// 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.
|
/// threshold value, where lower thresholds cause smaller/more distant voxels to be subdivided.
|
||||||
|
@ -526,281 +524,4 @@ public:
|
||||||
virtual QByteArray getImplementationClassName() const;
|
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
|
#endif // hifi_MetavoxelData_h
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "MetavoxelMessages.h"
|
#include "MetavoxelMessages.h"
|
||||||
|
#include "Spanner.h"
|
||||||
|
|
||||||
void MetavoxelEditMessage::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
void MetavoxelEditMessage::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||||
static_cast<const MetavoxelEdit*>(edit.data())->apply(data, objects);
|
static_cast<const MetavoxelEdit*>(edit.data())->apply(data, objects);
|
||||||
|
@ -754,7 +755,7 @@ int HeightfieldClearFetchVisitor::visit(MetavoxelInfo& info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create spanner if necessary
|
// create spanner if necessary
|
||||||
Heightfield* spanner = static_cast<Heightfield*>(_spanner.data());
|
TempHeightfield* spanner = static_cast<TempHeightfield*>(_spanner.data());
|
||||||
float increment = 1.0f / heightScale;
|
float increment = 1.0f / heightScale;
|
||||||
if (!spanner) {
|
if (!spanner) {
|
||||||
_spannerBounds.minimum = glm::floor(_bounds.minimum / increment) * increment;
|
_spannerBounds.minimum = glm::floor(_bounds.minimum / increment) * increment;
|
||||||
|
@ -767,7 +768,7 @@ int HeightfieldClearFetchVisitor::visit(MetavoxelInfo& info) {
|
||||||
Box innerBounds = _spannerBounds;
|
Box innerBounds = _spannerBounds;
|
||||||
innerBounds.maximum.x -= increment;
|
innerBounds.maximum.x -= increment;
|
||||||
innerBounds.maximum.z -= 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),
|
QByteArray(heightfieldArea * DataBlock::COLOR_BYTES, 0), QByteArray(heightfieldArea, 0),
|
||||||
QVector<SharedObjectPointer>());
|
QVector<SharedObjectPointer>());
|
||||||
}
|
}
|
||||||
|
|
783
libraries/metavoxels/src/Spanner.cpp
Normal file
783
libraries/metavoxels/src/Spanner.cpp
Normal 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));
|
||||||
|
}
|
454
libraries/metavoxels/src/Spanner.h
Normal file
454
libraries/metavoxels/src/Spanner.h
Normal 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
|
|
@ -81,7 +81,7 @@ PacketVersion versionForPacketType(PacketType type) {
|
||||||
case PacketTypeAudioStreamStats:
|
case PacketTypeAudioStreamStats:
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeMetavoxelData:
|
case PacketTypeMetavoxelData:
|
||||||
return 8;
|
return 9;
|
||||||
case PacketTypeVoxelData:
|
case PacketTypeVoxelData:
|
||||||
return VERSION_VOXELS_HAS_FILE_BREAKS;
|
return VERSION_VOXELS_HAS_FILE_BREAKS;
|
||||||
default:
|
default:
|
||||||
|
|
Loading…
Reference in a new issue