Working on transitioning functions to quadtree equivalents.

This commit is contained in:
Andrzej Kapolka 2014-12-01 16:43:03 -08:00
parent 6be1810967
commit 3fbbd6c2de
4 changed files with 252 additions and 202 deletions

View file

@ -151,8 +151,8 @@ void SharedObject::writeExtraSubdivision(Bitstream& out) {
// nothing by default
}
void SharedObject::readExtraSubdivision(Bitstream& in) {
// nothing by default
SharedObject* SharedObject::readExtraSubdivision(Bitstream& in) {
return this;
}
QAtomicInt SharedObject::_nextID(1);

View file

@ -96,7 +96,8 @@ public:
virtual void writeExtraSubdivision(Bitstream& out);
/// Reads the subdivision of the non-property contents of this object from the specified stream.
virtual void readExtraSubdivision(Bitstream& in);
/// \return the modified object, or this if no modification was performed
virtual SharedObject* readExtraSubdivision(Bitstream& in);
private:

View file

@ -1242,6 +1242,235 @@ bool HeightfieldNode::isLeaf() const {
return true;
}
float HeightfieldNode::getHeight(const glm::vec3& location) const {
if (!isLeaf()) {
if (location.x < 0.5f) {
if (location.z < 0.5f) {
return _children[0]->getHeight(location * 2.0f);
} else {
return _children[Y_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(0.0f, 0.0f, 1.0f));
}
} else {
if (location.z < 0.5f) {
return _children[X_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(1.0f, 0.0f, 0.0f));
} else {
return _children[X_MAXIMUM_FLAG | Y_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(1.0f, 0.0f, 1.0f));
}
}
}
if (!_height) {
return -FLT_MAX;
}
int width = _height->getWidth();
const QVector<quint16>& contents = _height->getContents();
const quint16* src = contents.constData();
int height = contents.size() / width;
int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION;
int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION;
glm::vec3 relative = location;
relative.x = relative.x * innerWidth + HeightfieldHeight::HEIGHT_BORDER;
relative.z = relative.z * innerHeight + HeightfieldHeight::HEIGHT_BORDER;
if (relative.x < 0.0f || relative.z < 0.0f || relative.x > width - 1 || relative.z > height - 1) {
return -FLT_MAX;
}
// find the bounds of the cell containing the point and the shared vertex heights
glm::vec3 floors = glm::floor(relative);
glm::vec3 ceils = glm::ceil(relative);
glm::vec3 fracts = glm::fract(relative);
int floorX = (int)floors.x;
int floorZ = (int)floors.z;
int ceilX = (int)ceils.x;
int ceilZ = (int)ceils.z;
float upperLeft = src[floorZ * width + floorX];
float lowerRight = src[ceilZ * width + ceilX];
float interpolatedHeight = glm::mix(upperLeft, lowerRight, fracts.z);
// the final vertex (and thus which triangle we check) depends on which half we're on
if (fracts.x >= fracts.z) {
float upperRight = src[floorZ * width + ceilX];
interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z),
(fracts.x - fracts.z) / (1.0f - fracts.z));
} else {
float lowerLeft = src[ceilZ * width + floorX];
interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, fracts.x / fracts.z);
}
if (interpolatedHeight == 0.0f) {
return -FLT_MAX; // ignore zero values
}
return interpolatedHeight / numeric_limits<quint16>::max();
}
bool HeightfieldNode::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
if (!isLeaf()) {
float closestDistance = FLT_MAX;
for (int i = 0; i < CHILD_COUNT; i++) {
float childDistance;
if (_children[i]->findRayIntersection(origin * glm::vec3(2.0f, 1.0f, 2.0f) -
glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f),
direction * glm::vec3(2.0f, 1.0f, 2.0f), childDistance)) {
closestDistance = qMin(closestDistance, childDistance);
}
}
if (closestDistance == FLT_MAX) {
return false;
}
distance = closestDistance;
return true;
}
if (!_height) {
return false;
}
int width = _height->getWidth();
const QVector<quint16>& contents = _height->getContents();
const quint16* src = contents.constData();
int height = contents.size() / width;
int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION;
int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION;
int highestX = innerWidth + HeightfieldHeight::HEIGHT_BORDER;
int highestZ = innerHeight + HeightfieldHeight::HEIGHT_BORDER;
glm::vec3 scale((float)innerWidth, (float)numeric_limits<quint16>::max(), (float)innerHeight);
glm::vec3 dir = direction * scale;
glm::vec3 entry = origin * scale;
float boundsDistance;
if (!Box(glm::vec3(), glm::vec3((float)innerWidth, (float)numeric_limits<quint16>::max(),
(float)innerHeight)).findRayIntersection(entry, dir, boundsDistance)) {
return false;
}
entry += dir * boundsDistance;
entry.x += HeightfieldHeight::HEIGHT_BORDER;
entry.z += HeightfieldHeight::HEIGHT_BORDER;
glm::vec3 floors = glm::floor(entry);
glm::vec3 ceils = glm::ceil(entry);
if (floors.x == ceils.x) {
if (dir.x > 0.0f) {
ceils.x += 1.0f;
} else {
floors.x -= 1.0f;
}
}
if (floors.z == ceils.z) {
if (dir.z > 0.0f) {
ceils.z += 1.0f;
} else {
floors.z -= 1.0f;
}
}
bool withinBounds = true;
float accumulatedDistance = 0.0f;
while (withinBounds) {
// find the heights at the corners of the current cell
int floorX = qMin(qMax((int)floors.x, HeightfieldHeight::HEIGHT_BORDER), highestX);
int floorZ = qMin(qMax((int)floors.z, HeightfieldHeight::HEIGHT_BORDER), highestZ);
int ceilX = qMin(qMax((int)ceils.x, HeightfieldHeight::HEIGHT_BORDER), highestX);
int ceilZ = qMin(qMax((int)ceils.z, HeightfieldHeight::HEIGHT_BORDER), highestZ);
float upperLeft = src[floorZ * width + floorX];
float upperRight = src[floorZ * width + ceilX];
float lowerLeft = src[ceilZ * width + floorX];
float lowerRight = src[ceilZ * width + ceilX];
// find the distance to the next x coordinate
float xDistance = FLT_MAX;
if (dir.x > 0.0f) {
xDistance = (ceils.x - entry.x) / dir.x;
} else if (dir.x < 0.0f) {
xDistance = (floors.x - entry.x) / dir.x;
}
// and the distance to the next z coordinate
float zDistance = FLT_MAX;
if (dir.z > 0.0f) {
zDistance = (ceils.z - entry.z) / dir.z;
} else if (dir.z < 0.0f) {
zDistance = (floors.z - entry.z) / dir.z;
}
// the exit distance is the lower of those two
float exitDistance = qMin(xDistance, zDistance);
glm::vec3 exit, nextFloors = floors, nextCeils = ceils;
if (exitDistance == FLT_MAX) {
if (dir.y > 0.0f) {
return false; // line points upwards; no collisions possible
}
withinBounds = false; // line points downwards; check this cell only
} else {
// find the exit point and the next cell, and determine whether it's still within the bounds
exit = entry + exitDistance * dir;
withinBounds = (exit.y >= 0.0f && exit.y <= numeric_limits<quint16>::max());
if (exitDistance == xDistance) {
if (dir.x > 0.0f) {
nextFloors.x += 1.0f;
withinBounds &= (nextCeils.x += 1.0f) <= highestX;
} else {
withinBounds &= (nextFloors.x -= 1.0f) >= HeightfieldHeight::HEIGHT_BORDER;
nextCeils.x -= 1.0f;
}
}
if (exitDistance == zDistance) {
if (dir.z > 0.0f) {
nextFloors.z += 1.0f;
withinBounds &= (nextCeils.z += 1.0f) <= highestZ;
} else {
withinBounds &= (nextFloors.z -= 1.0f) >= HeightfieldHeight::HEIGHT_BORDER;
nextCeils.z -= 1.0f;
}
}
// check the vertical range of the ray against the ranges of the cell heights
if (qMin(entry.y, exit.y) > qMax(qMax(upperLeft, upperRight), qMax(lowerLeft, lowerRight)) ||
qMax(entry.y, exit.y) < qMin(qMin(upperLeft, upperRight), qMin(lowerLeft, lowerRight))) {
entry = exit;
floors = nextFloors;
ceils = nextCeils;
accumulatedDistance += exitDistance;
continue;
}
}
// having passed the bounds check, we must check against the planes
glm::vec3 relativeEntry = entry - glm::vec3(floors.x, upperLeft, floors.z);
// first check the triangle including the Z+ segment
glm::vec3 lowerNormal(lowerLeft - lowerRight, 1.0f, upperLeft - lowerLeft);
float lowerProduct = glm::dot(lowerNormal, dir);
if (lowerProduct < 0.0f) {
float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct;
glm::vec3 intersection = relativeEntry + planeDistance * dir;
if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f &&
intersection.z >= intersection.x) {
distance = boundsDistance + accumulatedDistance + planeDistance;
return true;
}
}
// then the one with the X+ segment
glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight);
float upperProduct = glm::dot(upperNormal, dir);
if (upperProduct < 0.0f) {
float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct;
glm::vec3 intersection = relativeEntry + planeDistance * dir;
if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f &&
intersection.x >= intersection.z) {
distance = boundsDistance + accumulatedDistance + planeDistance;
return true;
}
}
// no joy; continue on our way
entry = exit;
floors = nextFloors;
ceils = nextCeils;
accumulatedDistance += exitDistance;
}
return false;
}
void HeightfieldNode::read(HeightfieldStreamState& state) {
clearChildren();
@ -1704,206 +1933,16 @@ bool Heightfield::isHeightfield() const {
}
float Heightfield::getHeight(const glm::vec3& location) const {
if (!_height) {
return -FLT_MAX;
}
int width = _height->getWidth();
const QVector<quint16>& contents = _height->getContents();
const quint16* src = contents.constData();
int height = contents.size() / width;
int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION;
int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION;
glm::vec3 relative = glm::inverse(getRotation()) * (location - getTranslation()) * glm::vec3(1.0f / getScale(),
1.0f, 1.0f / (getScale() * _aspectZ));
relative.x = relative.x * innerWidth + HeightfieldHeight::HEIGHT_BORDER;
relative.z = relative.z * innerHeight + HeightfieldHeight::HEIGHT_BORDER;
if (relative.x < 0.0f || relative.z < 0.0f || relative.x > width - 1 || relative.z > height - 1) {
return -FLT_MAX;
}
// find the bounds of the cell containing the point and the shared vertex heights
glm::vec3 floors = glm::floor(relative);
glm::vec3 ceils = glm::ceil(relative);
glm::vec3 fracts = glm::fract(relative);
int floorX = (int)floors.x;
int floorZ = (int)floors.z;
int ceilX = (int)ceils.x;
int ceilZ = (int)ceils.z;
float upperLeft = src[floorZ * width + floorX];
float lowerRight = src[ceilZ * width + ceilX];
float interpolatedHeight = glm::mix(upperLeft, lowerRight, fracts.z);
// the final vertex (and thus which triangle we check) depends on which half we're on
if (fracts.x >= fracts.z) {
float upperRight = src[floorZ * width + ceilX];
interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z),
(fracts.x - fracts.z) / (1.0f - fracts.z));
} else {
float lowerLeft = src[ceilZ * width + floorX];
interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, fracts.x / fracts.z);
}
if (interpolatedHeight == 0.0f) {
return -FLT_MAX; // ignore zero values
}
// convert the interpolated height into world space
return getTranslation().y + interpolatedHeight * getScale() * _aspectY / numeric_limits<quint16>::max();
float result = _root->getHeight(glm::inverse(getRotation()) * (location - getTranslation()) * glm::vec3(1.0f / getScale(),
0.0f, 1.0f / (getScale() * _aspectZ)));
return (result == -FLT_MAX) ? -FLT_MAX : (getTranslation().y + result * getScale() * _aspectY);
}
bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
if (!_height) {
return false;
}
int width = _height->getWidth();
const QVector<quint16>& contents = _height->getContents();
const quint16* src = contents.constData();
int height = contents.size() / width;
int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION;
int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION;
int highestX = innerWidth + HeightfieldHeight::HEIGHT_BORDER;
int highestZ = innerHeight + HeightfieldHeight::HEIGHT_BORDER;
glm::quat inverseRotation = glm::inverse(getRotation());
glm::vec3 inverseScale(innerWidth / getScale(), numeric_limits<quint16>::max() / (getScale() * _aspectY),
innerHeight / (getScale() * _aspectZ));
glm::vec3 dir = inverseRotation * direction * inverseScale;
glm::vec3 entry = inverseRotation * (origin - getTranslation()) * inverseScale;
float boundsDistance;
if (!Box(glm::vec3(), glm::vec3((float)innerWidth, (float)numeric_limits<quint16>::max(),
(float)innerHeight)).findRayIntersection(entry, dir, boundsDistance)) {
return false;
}
entry += dir * boundsDistance;
entry.x += HeightfieldHeight::HEIGHT_BORDER;
entry.z += HeightfieldHeight::HEIGHT_BORDER;
glm::vec3 floors = glm::floor(entry);
glm::vec3 ceils = glm::ceil(entry);
if (floors.x == ceils.x) {
if (dir.x > 0.0f) {
ceils.x += 1.0f;
} else {
floors.x -= 1.0f;
}
}
if (floors.z == ceils.z) {
if (dir.z > 0.0f) {
ceils.z += 1.0f;
} else {
floors.z -= 1.0f;
}
}
bool withinBounds = true;
float accumulatedDistance = 0.0f;
while (withinBounds) {
// find the heights at the corners of the current cell
int floorX = qMin(qMax((int)floors.x, HeightfieldHeight::HEIGHT_BORDER), highestX);
int floorZ = qMin(qMax((int)floors.z, HeightfieldHeight::HEIGHT_BORDER), highestZ);
int ceilX = qMin(qMax((int)ceils.x, HeightfieldHeight::HEIGHT_BORDER), highestX);
int ceilZ = qMin(qMax((int)ceils.z, HeightfieldHeight::HEIGHT_BORDER), highestZ);
float upperLeft = src[floorZ * width + floorX];
float upperRight = src[floorZ * width + ceilX];
float lowerLeft = src[ceilZ * width + floorX];
float lowerRight = src[ceilZ * width + ceilX];
// find the distance to the next x coordinate
float xDistance = FLT_MAX;
if (dir.x > 0.0f) {
xDistance = (ceils.x - entry.x) / dir.x;
} else if (dir.x < 0.0f) {
xDistance = (floors.x - entry.x) / dir.x;
}
// and the distance to the next z coordinate
float zDistance = FLT_MAX;
if (dir.z > 0.0f) {
zDistance = (ceils.z - entry.z) / dir.z;
} else if (dir.z < 0.0f) {
zDistance = (floors.z - entry.z) / dir.z;
}
// the exit distance is the lower of those two
float exitDistance = qMin(xDistance, zDistance);
glm::vec3 exit, nextFloors = floors, nextCeils = ceils;
if (exitDistance == FLT_MAX) {
if (dir.y > 0.0f) {
return false; // line points upwards; no collisions possible
}
withinBounds = false; // line points downwards; check this cell only
} else {
// find the exit point and the next cell, and determine whether it's still within the bounds
exit = entry + exitDistance * dir;
withinBounds = (exit.y >= 0.0f && exit.y <= numeric_limits<quint16>::max());
if (exitDistance == xDistance) {
if (dir.x > 0.0f) {
nextFloors.x += 1.0f;
withinBounds &= (nextCeils.x += 1.0f) <= highestX;
} else {
withinBounds &= (nextFloors.x -= 1.0f) >= HeightfieldHeight::HEIGHT_BORDER;
nextCeils.x -= 1.0f;
}
}
if (exitDistance == zDistance) {
if (dir.z > 0.0f) {
nextFloors.z += 1.0f;
withinBounds &= (nextCeils.z += 1.0f) <= highestZ;
} else {
withinBounds &= (nextFloors.z -= 1.0f) >= HeightfieldHeight::HEIGHT_BORDER;
nextCeils.z -= 1.0f;
}
}
// check the vertical range of the ray against the ranges of the cell heights
if (qMin(entry.y, exit.y) > qMax(qMax(upperLeft, upperRight), qMax(lowerLeft, lowerRight)) ||
qMax(entry.y, exit.y) < qMin(qMin(upperLeft, upperRight), qMin(lowerLeft, lowerRight))) {
entry = exit;
floors = nextFloors;
ceils = nextCeils;
accumulatedDistance += exitDistance;
continue;
}
}
// having passed the bounds check, we must check against the planes
glm::vec3 relativeEntry = entry - glm::vec3(floors.x, upperLeft, floors.z);
// first check the triangle including the Z+ segment
glm::vec3 lowerNormal(lowerLeft - lowerRight, 1.0f, upperLeft - lowerLeft);
float lowerProduct = glm::dot(lowerNormal, dir);
if (lowerProduct < 0.0f) {
float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct;
glm::vec3 intersection = relativeEntry + planeDistance * dir;
if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f &&
intersection.z >= intersection.x) {
distance = boundsDistance + accumulatedDistance + planeDistance;
return true;
}
}
// then the one with the X+ segment
glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight);
float upperProduct = glm::dot(upperNormal, dir);
if (upperProduct < 0.0f) {
float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct;
glm::vec3 intersection = relativeEntry + planeDistance * dir;
if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f &&
intersection.x >= intersection.z) {
distance = boundsDistance + accumulatedDistance + planeDistance;
return true;
}
}
// no joy; continue on our way
entry = exit;
floors = nextFloors;
ceils = nextCeils;
accumulatedDistance += exitDistance;
}
return false;
glm::vec3 inverseScale(1.0f / getScale(), 1.0f / (getScale() * _aspectY), 1.0f / (getScale() * _aspectZ));
return _root->findRayIntersection(inverseRotation * (origin - getTranslation()) * inverseScale,
inverseRotation * direction * inverseScale, distance);
}
Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius,
@ -2747,7 +2786,7 @@ void Heightfield::writeExtraSubdivision(Bitstream& out) {
}
}
void Heightfield::readExtraSubdivision(Bitstream& in) {
SharedObject* Heightfield::readExtraSubdivision(Bitstream& in) {
MetavoxelLOD lod, referenceLOD;
if (in.getContext()) {
MetavoxelStreamBase* base = static_cast<MetavoxelStreamBase*>(in.getContext());
@ -2758,8 +2797,14 @@ void Heightfield::readExtraSubdivision(Bitstream& in) {
HeightfieldStreamState state = { base, glm::vec2(), 1.0f };
if (state.becameSubdividedOrCollapsed()) {
setRoot(HeightfieldNodePointer(_root->readSubdivision(state)));
HeightfieldNodePointer root(_root->readSubdivision(state));
if (_root != root) {
Heightfield* newHeightfield = static_cast<Heightfield*>(clone(true));
newHeightfield->setRoot(root);
return newHeightfield;
}
}
return this;
}
QByteArray Heightfield::getRendererClassName() const {

View file

@ -505,6 +505,10 @@ public:
const HeightfieldNodePointer& getChild(int index) const { return _children[index]; }
float getHeight(const glm::vec3& location) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
void read(HeightfieldStreamState& state);
void write(HeightfieldStreamState& state) const;
@ -593,7 +597,7 @@ public:
virtual void writeExtraDelta(Bitstream& out, const SharedObject* reference) const;
virtual void readExtraDelta(Bitstream& in, const SharedObject* reference);
virtual void writeExtraSubdivision(Bitstream& out);
virtual void readExtraSubdivision(Bitstream& in);
virtual SharedObject* readExtraSubdivision(Bitstream& in);
signals: