Merge pull request #16032 from sethalves/fix-polyvox-state-machine

BUGZ-1176: rework polyvox state-machine
This commit is contained in:
Shannon Romano 2019-09-05 10:04:47 -07:00 committed by GitHub
commit ef9b81ca0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 475 additions and 229 deletions

View file

@ -72,7 +72,6 @@
#include "EntityTreeRenderer.h"
#include "RenderablePolyVoxEntityItem.h"
#include "EntityEditPacketSender.h"
#include "PhysicalEntitySimulation.h"
const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
@ -83,30 +82,60 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
_voxelData -- compressed QByteArray representation of which voxels have which values
_volData -- datastructure from the PolyVox library which holds which voxels have which values
_mesh -- renderable representation of the voxels
_shape -- used for bullet collisions
_shape -- used for bullet (physics) collisions
Each one depends on the one before it, except that _voxelData is set from _volData if a script edits the voxels.
There are booleans to indicate that something has been updated and the dependents now need to be updated.
_meshReady -- do we have something to give scripts that ask for the mesh?
_voxelDataDirty -- do we need to uncompress data and expand it into _volData?
_volDataDirty -- does recomputeMesh need to be called?
_shapeReady -- are we ready to tell bullet our shape?
_voxelDataDirty
_volDataDirty
_meshDirty
In RenderablePolyVoxEntityItem::render, these flags are checked and changes are propagated along the chain.
decompressVolumeData() is called to decompress _voxelData into _volData. recomputeMesh() is called to invoke the
polyVox surface extractor to create _mesh (as well as set Simulation _flags). Because Simulation::DIRTY_SHAPE
is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on
the surface style.
Here is a simplified diagram of the state machine implemented in RenderablePolyVoxEntityItem::update
When a script changes _volData, compressVolumeDataAndSendEditPacket is called to update _voxelData and to
send a packet to the entity-server.
+-------------------+
| |
| |
| volDataDirty | voxelDataDirty
| +--v--+
| +------+Ready+--------+
| | +-----+ |
| | |
| +-----v----+ +------v------+
| |BakingMesh| |Uncompressing|
| +-----+----+ +------+------+
| | |
| | |
| +---v-------+ +-------v------------+
| |Compressing| |BakingMeshNoCompress|
| +---------+-+ ++-------------------+
| | |
| | |
| +-v--------v+
| |BakingShape|
| +-----+-----+
| |
+-------------------+
decompressVolumeData, recomputeMesh, computeShapeInfoWorker, and compressVolumeDataAndSendEditPacket are too expensive
to run on a thread that has other things to do. These use QtConcurrent::run to spawn a thread. As each thread
finishes, it adjusts the dirty flags so that the next call to render() will kick off the next step.
polyvoxes are designed to seemlessly fit up against neighbors. If voxels go right up to the edge of polyvox,
The work for each step is done on temporary worker threads. The PolyVox entity will update _updateNeeded and
enable or disable update calls on the entity, depending on if there is more work to do.
From the 'Ready' state, if we receive an update from the network, _voxelDataDirty will be set true. We
uncompress the received data, bake the mesh (for the render-engine's benefit), and then compute the shape
(for the physics-engine's benefit). This is the right-hand side of the diagram.
From the 'Ready' state, if a script changes a voxel, _volDataDirty will be set true. We bake the mesh,
compress the voxels into a new _voxelData, and transmit the new _voxelData to the entity-server. We then
bake the shape. This is the left-hand side of the diagram.
The actual state machine is more complicated than the diagram, because it's possible for _volDataDirty or
_voxelDataDirty to be set true while worker threads are attempting to bake meshes or shapes. If this happens,
we jump back to a higher point in the diagram to avoid wasting effort.
PolyVoxes are designed to seemlessly fit up against neighbors. If voxels go right up to the edge of polyvox,
the resulting mesh wont be closed -- the library assumes you'll have another polyvox next to it to continue the
mesh.
@ -114,8 +143,8 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
previously mentioned gaps along the edges.
Non-edged polyvox entities can be told about their neighbors in all 6 cardinal directions. On the positive
edges of the polyvox, the values are set from the (negative edge of) relevant neighbor so that their meshes
knit together. This is handled by bonkNeighbors and copyUpperEdgesFromNeighbors. In these functions, variable
edges of the polyvox, the values are set from the (negative edge of the) relevant neighbor so that their meshes
knit together. This is handled by tellNeighborsToRecopyEdges and copyUpperEdgesFromNeighbors. In these functions, variable
names have XP for x-positive, XN x-negative, etc.
*/
@ -140,7 +169,8 @@ EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entit
return entity;
}
RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID) : PolyVoxEntityItem(entityItemID) { }
RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID) : PolyVoxEntityItem(entityItemID) {
}
RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() {
withWriteLock([&] {
@ -165,11 +195,12 @@ bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) {
}
void RenderablePolyVoxEntityItem::setVoxelData(const QByteArray& voxelData) {
// compressed voxel information from the entity-server
// accept compressed voxel information from the entity-server
withWriteLock([&] {
if (_voxelData != voxelData) {
_voxelData = voxelData;
_voxelDataDirty = true;
startUpdates();
}
});
}
@ -189,21 +220,22 @@ void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxel
bool willBeEdged = isEdged(voxelSurfaceStyle);
if (wasEdged != willBeEdged) {
_volDataDirty = true;
_volData.reset();
_voxelSurfaceStyle = voxelSurfaceStyle;
_voxelDataDirty = true;
volSizeChanged = true;
} else {
_volDataDirty = true;
_voxelSurfaceStyle = voxelSurfaceStyle;
}
_voxelSurfaceStyle = voxelSurfaceStyle;
startUpdates();
});
if (volSizeChanged) {
// setVoxelVolumeSize will re-alloc _volData with the right size
setVoxelVolumeSize(_voxelVolumeSize);
}
_updateFromNeighborXEdge = _updateFromNeighborYEdge = _updateFromNeighborZEdge = true;
tellNeighborsToRecopyEdges(true);
startUpdates();
}
glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const {
@ -262,9 +294,6 @@ bool RenderablePolyVoxEntityItem::setVoxel(const ivec3& v, uint8_t toValue) {
withWriteLock([&] {
result = setVoxelInternal(v, toValue);
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -311,9 +340,6 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
result |= setVoxelInternal(v, toValue);
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -336,9 +362,6 @@ bool RenderablePolyVoxEntityItem::setCuboid(const glm::vec3& lowPosition, const
result |= setVoxelInternal(v, toValue);
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -374,9 +397,6 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(const vec3& center, float ra
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -434,9 +454,6 @@ bool RenderablePolyVoxEntityItem::setSphere(const vec3& centerWorldCoords, float
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -482,9 +499,6 @@ bool RenderablePolyVoxEntityItem::setCapsule(const vec3& startWorldCoords, const
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -710,8 +724,9 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const {
void RenderablePolyVoxEntityItem::setRegistrationPoint(const glm::vec3& value) {
if (value != _registrationPoint) {
_meshDirty = true;
_shapeReady = false;
EntityItem::setRegistrationPoint(value);
startUpdates();
}
}
@ -721,15 +736,11 @@ bool RenderablePolyVoxEntityItem::isReadyToComputeShape() const {
return true;
}
// we determine if we are ready to compute the physics shape by actually doing so.
// if _voxelDataDirty or _volDataDirty is set, don't do this yet -- wait for their
// threads to finish before creating the collision shape.
if (_meshDirty && !_voxelDataDirty && !_volDataDirty) {
const_cast<RenderablePolyVoxEntityItem*>(this)->_meshDirty = false;
const_cast<RenderablePolyVoxEntityItem*>(this)->computeShapeInfoWorker();
return false;
}
return true;
bool result;
withReadLock([&] {
result = _shapeReady;
});
return result;
}
void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
@ -739,33 +750,155 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
return;
}
// the shape was actually computed in isReadyToComputeShape. Just hand it off, here.
withWriteLock([&] {
withReadLock([&] {
info = _shapeInfo;
});
}
bool RenderablePolyVoxEntityItem::updateDependents() {
bool voxelDataDirty;
bool volDataDirty;
void RenderablePolyVoxEntityItem::changeUpdates(bool value) {
if (_updateNeeded != value) {
EntityTreePointer entityTree = getTree();
if (entityTree) {
EntitySimulationPointer simulation = entityTree->getSimulation();
if (simulation) {
_updateNeeded = value;
_flags |= Simulation::DIRTY_UPDATEABLE;
simulation->changeEntity(getThisPointer());
}
}
}
}
void RenderablePolyVoxEntityItem::startUpdates() {
changeUpdates(true);
}
void RenderablePolyVoxEntityItem::stopUpdates() {
changeUpdates(false);
}
void RenderablePolyVoxEntityItem::update(const quint64& now) {
bool doRecomputeMesh { false };
bool doUncompress { false };
bool doCompress { false };
bool doRecomputeShape { false };
withWriteLock([&] {
voxelDataDirty = _voxelDataDirty;
volDataDirty = _volDataDirty;
if (_voxelDataDirty) {
_voxelDataDirty = false;
} else if (_volDataDirty) {
_volDataDirty = false;
} else {
_meshReady = true;
tellNeighborsToRecopyEdges(false);
switch (_state) {
case PolyVoxState::Ready: {
if (_volDataDirty) {
_volDataDirty = _voxelDataDirty = false;
_state = PolyVoxState::BakingMesh;
doRecomputeMesh = true;
} else if (_voxelDataDirty) {
_voxelDataDirty = false;
_state = PolyVoxState::Uncompressing;
doUncompress = true;
} else {
copyUpperEdgesFromNeighbors();
if (!_volDataDirty && !_voxelDataDirty) {
// nothing to do
stopUpdates();
}
}
break;
}
case PolyVoxState::Uncompressing: {
break; // wait
}
case PolyVoxState::UncompressingFinished: {
if (_volDataDirty) {
_volDataDirty = _voxelDataDirty = false;
_state = PolyVoxState::BakingMeshNoCompress;
doRecomputeMesh = true;
} else if (_voxelDataDirty) {
_voxelDataDirty = false;
// _voxelData changed while we were uncompressing the previous version, uncompress again
_state = PolyVoxState::Uncompressing;
doUncompress = true;
} else {
_state = PolyVoxState::Ready;
}
break;
}
case PolyVoxState::BakingMesh: {
break; // wait
}
case PolyVoxState::BakingMeshFinished: {
if (_volDataDirty) {
_volDataDirty = _voxelDataDirty = false;
_state = PolyVoxState::BakingMesh;
// a local edit happened while we were baking the mesh. rebake mesh...
doRecomputeMesh = true;
} else if (_voxelDataDirty) {
_voxelDataDirty = false;
// we received a change from the wire while baking the mesh.
_state = PolyVoxState::Uncompressing;
doUncompress = true;
} else {
_state = PolyVoxState::Compressing;
doCompress = true;
}
break;
}
case PolyVoxState::BakingMeshNoCompress: {
break; // wait
}
case PolyVoxState::BakingMeshNoCompressFinished: {
if (_volDataDirty) {
_volDataDirty = _voxelDataDirty = false;
_state = PolyVoxState::BakingMesh;
// a local edit happened while we were baking the mesh. rebake mesh...
doRecomputeMesh = true;
} else if (_voxelDataDirty) {
_voxelDataDirty = false;
// we received a change from the wire while baking the mesh.
_state = PolyVoxState::Uncompressing;
doUncompress = true;
} else {
_state = PolyVoxState::BakingShape;
doRecomputeShape = true;
}
break;
}
case PolyVoxState::Compressing: {
break; // wait
}
case PolyVoxState::CompressingFinished: {
_state = PolyVoxState::BakingShape;
doRecomputeShape = true;
break;
}
case PolyVoxState::BakingShape: {
break; // wait
}
case PolyVoxState::BakingShapeFinished: {
_state = PolyVoxState::Ready;
break;
}
}
});
if (voxelDataDirty) {
decompressVolumeData();
} else if (volDataDirty) {
if (doRecomputeMesh) {
recomputeMesh();
}
return !volDataDirty;
if (doUncompress) {
uncompressVolumeData();
}
if (doCompress) {
compressVolumeDataAndSendEditPacket();
}
if (doRecomputeShape) {
computeShapeInfoWorker();
}
}
void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) {
@ -782,6 +915,8 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolum
_voxelVolumeSize = voxelVolumeSize;
_volData.reset();
_onCount = 0;
_updateFromNeighborXEdge = _updateFromNeighborYEdge = _updateFromNeighborZEdge = true;
startUpdates();
static const PolyVox::Vector3DInt32 lowCorner(0, 0, 0);
PolyVox::Vector3DInt32 highCorner;
@ -806,6 +941,8 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolum
// having the "outside of voxel-space" value be 255 has helped me notice some problems.
_volData->setBorderValue(255);
});
tellNeighborsToRecopyEdges(true);
}
bool inUserBounds(const std::shared_ptr<PolyVox::SimpleVolume<uint8_t>> vol,
@ -853,32 +990,38 @@ uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(const ivec3& v) const {
}
void RenderablePolyVoxEntityItem::setVoxelMarkNeighbors(int x, int y, int z, uint8_t toValue) {
_volData->setVoxelAt(x, y, z, toValue);
if (x == 0) {
_neighborXNeedsUpdate = true;
startUpdates();
}
if (y == 0) {
_neighborYNeedsUpdate = true;
startUpdates();
}
if (z == 0) {
_neighborZNeedsUpdate = true;
startUpdates();
}
}
bool RenderablePolyVoxEntityItem::setVoxelInternal(const ivec3& v, uint8_t toValue) {
// set a voxel without recompressing the voxel data. This assumes that the caller has
// write-locked the entity.
bool result = false;
if (!inUserBounds(_volData, _voxelSurfaceStyle, v)) {
return result;
// set a voxel without recompressing the voxel data. This assumes that the caller has write-locked the entity.
bool result = updateOnCount(v, toValue);
if (result) {
if (isEdged()) {
setVoxelMarkNeighbors(v.x + 1, v.y + 1, v.z + 1, toValue);
} else {
setVoxelMarkNeighbors(v.x, v.y, v.z, toValue);
}
_volDataDirty = true;
startUpdates();
}
result = updateOnCount(v, toValue);
if (isEdged()) {
_volData->setVoxelAt(v.x + 1, v.y + 1, v.z + 1, toValue);
} else {
_volData->setVoxelAt(v.x, v.y, v.z, toValue);
}
if (glm::any(glm::equal(ivec3(0), v))) {
_neighborsNeedUpdate = true;
}
_volDataDirty |= result;
return result;
}
bool RenderablePolyVoxEntityItem::updateOnCount(const ivec3& v, uint8_t toValue) {
// keep _onCount up to date
if (!inUserBounds(_volData, _voxelSurfaceStyle, v)) {
@ -902,7 +1045,7 @@ bool RenderablePolyVoxEntityItem::updateOnCount(const ivec3& v, uint8_t toValue)
return false;
}
void RenderablePolyVoxEntityItem::decompressVolumeData() {
void RenderablePolyVoxEntityItem::uncompressVolumeData() {
// take compressed data and expand it into _volData.
QByteArray voxelData;
auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer());
@ -921,9 +1064,9 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION ||
voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION ||
voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) {
qCDebug(entitiesrenderer) << "voxelSize is not reasonable, skipping decompressions."
<< voxelXSize << voxelYSize << voxelZSize << getName() << getID();
entity->setVoxelDataDirty(false);
qCDebug(entitiesrenderer) << "voxelSize is not reasonable, skipping uncompressions."
<< voxelXSize << voxelYSize << voxelZSize << getName() << getID();
entity->setVoxelsFromData(QByteArray(1, 0), 1, 1, 1);
return;
}
int rawSize = voxelXSize * voxelYSize * voxelZSize;
@ -934,10 +1077,10 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
QByteArray uncompressedData = qUncompress(compressedData);
if (uncompressedData.size() != rawSize) {
qCDebug(entitiesrenderer) << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")"
qCDebug(entitiesrenderer) << "PolyVox uncompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")"
<< "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size()
<< getName() << getID();
entity->setVoxelDataDirty(false);
<< getName() << getID();
entity->setVoxelsFromData(QByteArray(1, 0), 1, 1, 1);
return;
}
@ -947,13 +1090,14 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
void RenderablePolyVoxEntityItem::setVoxelsFromData(QByteArray uncompressedData,
quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) {
// this accepts the payload from decompressVolumeData
// this accepts the payload from uncompressVolumeData
withWriteLock([&] {
loop3(ivec3(0), ivec3(voxelXSize, voxelYSize, voxelZSize), [&](const ivec3& v) {
int uncompressedIndex = (v.z * voxelYSize * voxelXSize) + (v.y * voxelZSize) + v.x;
setVoxelInternal(v, uncompressedData[uncompressedIndex]);
});
_volDataDirty = true;
_state = PolyVoxState::UncompressingFinished;
});
}
@ -972,10 +1116,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
voxelZSize = _voxelVolumeSize.z;
});
EntityTreeElementPointer element = getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] {
QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity] {
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
QByteArray uncompressedData = polyVoxEntity->volDataToArray(voxelXSize, voxelYSize, voxelZSize);
@ -992,17 +1133,37 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
// HACK -- until we have a way to allow for properties larger than MTU, don't update.
// revert the active voxel-space to the last version that fit.
qCDebug(entitiesrenderer) << "compressed voxel data is too large" << entity->getName() << entity->getID();
const auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
polyVoxEntity->compressVolumeDataFinished(QByteArray());
return;
}
auto now = usecTimestampNow();
entity->setLastEdited(now);
entity->setLastBroadcast(now);
std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->compressVolumeDataFinished(newVoxelData);
});
}
std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->setVoxelData(newVoxelData);
void RenderablePolyVoxEntityItem::compressVolumeDataFinished(const QByteArray& voxelData) {
// compressed voxel information from the entity-server
withWriteLock([&] {
if (voxelData.size() > 0 && _voxelData != voxelData) {
_voxelData = voxelData;
}
_state = PolyVoxState::CompressingFinished;
});
auto now = usecTimestampNow();
setLastEdited(now);
setLastBroadcast(now);
EntityTreeElementPointer element = getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
if (tree) {
tree->withReadLock([&] {
EntityItemProperties properties = entity->getProperties();
EntityPropertyFlags desiredProperties;
desiredProperties.setHasProperty(PROP_VOXEL_DATA);
EntityItemProperties properties = getProperties(desiredProperties, false);
properties.setVoxelDataDirty();
properties.setLastEdited(now);
@ -1010,12 +1171,13 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast<PhysicalEntitySimulation>(simulation);
EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr;
if (packetSender) {
packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, entity->getID(), properties);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, getID(), properties);
}
});
});
}
}
EntityItemPointer lookUpNeighbor(EntityTreePointer tree, EntityItemID neighborID, EntityItemWeakPointer& currentWP) {
EntityItemPointer current = currentWP.lock();
@ -1064,56 +1226,103 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() {
return;
}
auto currentXPNeighbor = getXPNeighbor();
auto currentYPNeighbor = getYPNeighbor();
auto currentZPNeighbor = getZPNeighbor();
if (currentXPNeighbor && currentXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
for (int y = 0; y < _volData->getHeight(); y++) {
for (int z = 0; z < _volData->getDepth(); z++) {
uint8_t neighborValue = currentXPNeighbor->getVoxel({ 0, y, z });
if ((y == 0 || z == 0) && _volData->getVoxelAt(_volData->getWidth() - 1, y, z) != neighborValue) {
bonkNeighbors();
}
_volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue);
}
}
});
if (!_updateFromNeighborXEdge && !_updateFromNeighborYEdge && !_updateFromNeighborZEdge) {
return;
}
cacheNeighbors();
if (currentYPNeighbor && currentYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
for (int x = 0; x < _volData->getWidth(); x++) {
for (int z = 0; z < _volData->getDepth(); z++) {
uint8_t neighborValue = currentYPNeighbor->getVoxel({ x, 0, z });
if ((x == 0 || z == 0) && _volData->getVoxelAt(x, _volData->getHeight() - 1, z) != neighborValue) {
bonkNeighbors();
}
_volData->setVoxelAt(x, _volData->getHeight() - 1, z, neighborValue);
}
}
});
}
if (currentZPNeighbor && currentZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
for (int x = 0; x < _volData->getWidth(); x++) {
if (_updateFromNeighborXEdge) {
_updateFromNeighborXEdge = false;
auto currentXPNeighbor = getXPNeighbor();
if (currentXPNeighbor && currentXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
int x = _volData->getWidth() - 1;
for (int y = 0; y < _volData->getHeight(); y++) {
uint8_t neighborValue = currentZPNeighbor->getVoxel({ x, y, 0 });
_volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue);
if ((x == 0 || y == 0) && _volData->getVoxelAt(x, y, _volData->getDepth() - 1) != neighborValue) {
bonkNeighbors();
for (int z = 0; z < _volData->getDepth(); z++) {
uint8_t neighborValue = currentXPNeighbor->getVoxel({ 0, y, z });
uint8_t prevValue = _volData->getVoxelAt(x, y, z);
if (prevValue != neighborValue) {
_volData->setVoxelAt(x, y, z, neighborValue);
_volDataDirty = true;
}
}
_volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue);
}
}
});
});
}
}
if (_updateFromNeighborYEdge) {
_updateFromNeighborYEdge = false;
auto currentYPNeighbor = getYPNeighbor();
if (currentYPNeighbor && currentYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
int y = _volData->getHeight() - 1;
for (int x = 0; x < _volData->getWidth(); x++) {
for (int z = 0; z < _volData->getDepth(); z++) {
uint8_t neighborValue = currentYPNeighbor->getVoxel({ x, 0, z });
uint8_t prevValue = _volData->getVoxelAt(x, y, z);
if (prevValue != neighborValue) {
_volData->setVoxelAt(x, y, z, neighborValue);
_volDataDirty = true;
}
}
}
});
}
}
if (_updateFromNeighborZEdge) {
_updateFromNeighborZEdge = false;
auto currentZPNeighbor = getZPNeighbor();
if (currentZPNeighbor && currentZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
int z = _volData->getDepth() - 1;
for (int x = 0; x < _volData->getWidth(); x++) {
for (int y = 0; y < _volData->getHeight(); y++) {
uint8_t neighborValue = currentZPNeighbor->getVoxel({ x, y, 0 });
uint8_t prevValue = _volData->getVoxelAt(x, y, z);
if (prevValue != neighborValue) {
_volData->setVoxelAt(x, y, z, neighborValue);
_volDataDirty = true;
}
}
}
});
}
}
}
void RenderablePolyVoxEntityItem::tellNeighborsToRecopyEdges(bool force) {
// if this polyvox has changed any of its voxels with a zero coord (in x, y, or z) notify neighbors, if there are any
if (force || _neighborXNeedsUpdate || _neighborYNeedsUpdate || _neighborZNeedsUpdate) {
cacheNeighbors();
if (force || _neighborXNeedsUpdate) {
_neighborXNeedsUpdate = false;
auto currentXNNeighbor = getXNNeighbor();
if (currentXNNeighbor) {
currentXNNeighbor->neighborXEdgeChanged();
}
}
if (force || _neighborYNeedsUpdate) {
_neighborYNeedsUpdate = false;
auto currentYNNeighbor = getYNNeighbor();
if (currentYNNeighbor) {
currentYNNeighbor->neighborYEdgeChanged();
}
}
if (force || _neighborZNeedsUpdate) {
_neighborZNeedsUpdate = false;
auto currentZNNeighbor = getZNNeighbor();
if (currentZNNeighbor) {
currentZNNeighbor->neighborZEdgeChanged();
}
}
}
}
void RenderablePolyVoxEntityItem::recomputeMesh() {
// use _volData to make a renderable mesh
PolyVoxSurfaceStyle voxelSurfaceStyle;
@ -1121,9 +1330,6 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
voxelSurfaceStyle = _voxelSurfaceStyle;
});
cacheNeighbors();
copyUpperEdgesFromNeighbors();
auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer());
QtConcurrent::run([entity, voxelSurfaceStyle] {
@ -1135,24 +1341,14 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
entity->withReadLock([&] {
PolyVox::SimpleVolume<uint8_t>* volData = entity->getVolData();
switch (voxelSurfaceStyle) {
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: {
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute();
break;
}
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: {
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute();
break;
}
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: {
PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute();
break;
}
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
case PolyVoxEntityItem::SURFACE_CUBIC: {
PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
@ -1180,7 +1376,6 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
mesh->setVertexBuffer(vertexBufferView);
// TODO -- use 3-byte normals rather than 3-float normals
mesh->addAttribute(gpu::Stream::NORMAL,
gpu::BufferView(vertexBufferPtr,
@ -1194,28 +1389,24 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
(graphics::Index)vecIndices.size(), // numIndices
(graphics::Index)0, // baseVertex
graphics::Mesh::TRIANGLES)); // topology
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part),
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), (gpu::Byte*) parts.data()),
gpu::Element::PART_DRAWCALL));
entity->setMesh(mesh);
});
}
void RenderablePolyVoxEntityItem::setMesh(graphics::MeshPointer mesh) {
// this catches the payload from recomputeMesh
bool neighborsNeedUpdate;
withWriteLock([&] {
if (!_collisionless) {
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
_shapeReady = false;
_mesh = mesh;
_meshDirty = true;
_state = PolyVoxState::BakingMeshFinished;
_meshReady = true;
neighborsNeedUpdate = _neighborsNeedUpdate;
_neighborsNeedUpdate = false;
startUpdates();
});
if (neighborsNeedUpdate) {
bonkNeighbors();
}
somethingChangedNotification();
}
@ -1223,9 +1414,6 @@ void RenderablePolyVoxEntityItem::setMesh(graphics::MeshPointer mesh) {
void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
// this creates a collision-shape for the physics engine. The shape comes from
// _volData for cubic extractors and from _mesh for marching-cube extractors
if (!_meshReady) {
return;
}
EntityItemPointer entity = getThisPointer();
@ -1295,8 +1483,8 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
const auto& x = v.x;
const auto& y = v.y;
const auto& z = v.z;
if (glm::all(glm::greaterThan(v, ivec3(0))) &&
glm::all(glm::lessThan(v, ivec3(voxelVolumeSize) - 1)) &&
if (glm::all(glm::greaterThan(v, ivec3(0))) &&
glm::all(glm::lessThan(v, ivec3(voxelVolumeSize) - 1)) &&
(polyVoxEntity->getVoxelInternal({ x - 1, y, z }) > 0) &&
(polyVoxEntity->getVoxelInternal({ x, y - 1, z }) > 0) &&
(polyVoxEntity->getVoxelInternal({ x, y, z - 1 }) > 0) &&
@ -1360,6 +1548,10 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection
// this catches the payload from computeShapeInfoWorker
if (pointCollection.isEmpty()) {
EntityItem::computeShapeInfo(_shapeInfo);
withWriteLock([&] {
_shapeReady = true;
_state = PolyVoxState::BakingShapeFinished;
});
return;
}
@ -1373,7 +1565,8 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection
QString::number(_registrationPoint.z);
_shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey);
_shapeInfo.setPointCollection(pointCollection);
_meshDirty = false;
_shapeReady = true;
_state = PolyVoxState::BakingShapeFinished;
});
}
@ -1384,7 +1577,8 @@ void RenderablePolyVoxEntityItem::setXNNeighborID(const EntityItemID& xNNeighbor
if (xNNeighborID != _xNNeighborID) {
PolyVoxEntityItem::setXNNeighborID(xNNeighborID);
cacheNeighbors();
_neighborXNeedsUpdate = true;
startUpdates();
}
}
@ -1395,7 +1589,8 @@ void RenderablePolyVoxEntityItem::setYNNeighborID(const EntityItemID& yNNeighbor
if (yNNeighborID != _yNNeighborID) {
PolyVoxEntityItem::setYNNeighborID(yNNeighborID);
cacheNeighbors();
_neighborYNeedsUpdate = true;
startUpdates();
}
}
@ -1406,7 +1601,8 @@ void RenderablePolyVoxEntityItem::setZNNeighborID(const EntityItemID& zNNeighbor
if (zNNeighborID != _zNNeighborID) {
PolyVoxEntityItem::setZNNeighborID(zNNeighborID);
cacheNeighbors();
_neighborZNeedsUpdate = true;
startUpdates();
}
}
@ -1416,7 +1612,8 @@ void RenderablePolyVoxEntityItem::setXPNeighborID(const EntityItemID& xPNeighbor
}
if (xPNeighborID != _xPNeighborID) {
PolyVoxEntityItem::setXPNeighborID(xPNeighborID);
_volDataDirty = true;
_updateFromNeighborXEdge = true;
startUpdates();
}
}
@ -1426,7 +1623,8 @@ void RenderablePolyVoxEntityItem::setYPNeighborID(const EntityItemID& yPNeighbor
}
if (yPNeighborID != _yPNeighborID) {
PolyVoxEntityItem::setYPNeighborID(yPNeighborID);
_volDataDirty = true;
_updateFromNeighborYEdge = true;
startUpdates();
}
}
@ -1436,7 +1634,8 @@ void RenderablePolyVoxEntityItem::setZPNeighborID(const EntityItemID& zPNeighbor
}
if (zPNeighborID != _zPNeighborID) {
PolyVoxEntityItem::setZPNeighborID(zPNeighborID);
_volDataDirty = true;
_updateFromNeighborZEdge = true;
startUpdates();
}
}
@ -1464,31 +1663,8 @@ std::shared_ptr<RenderablePolyVoxEntityItem> RenderablePolyVoxEntityItem::getZPN
return std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(_zPNeighbor.lock());
}
void RenderablePolyVoxEntityItem::bonkNeighbors() {
// flag neighbors to the negative of this entity as needing to rebake their meshes.
cacheNeighbors();
auto currentXNNeighbor = getXNNeighbor();
auto currentYNNeighbor = getYNNeighbor();
auto currentZNNeighbor = getZNNeighbor();
if (currentXNNeighbor) {
currentXNNeighbor->setVolDataDirty();
}
if (currentYNNeighbor) {
currentYNNeighbor->setVolDataDirty();
}
if (currentZNNeighbor) {
currentZNNeighbor->setVolDataDirty();
}
}
// deprecated
bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) {
if (!updateDependents()) {
return false;
}
bool success = false;
if (_mesh) {
MeshProxy* meshProxy = nullptr;
@ -1517,7 +1693,7 @@ bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) {
}
scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel() {
if (!updateDependents() || !_mesh) {
if (!_mesh) {
return scriptable::ScriptableModelBase();
}
@ -1628,9 +1804,6 @@ bool PolyVoxEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPo
}
void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
if (entity->_voxelDataDirty || entity->_volDataDirty) {
entity->updateDependents();
}
#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT
if (!_hasTransitioned) {
@ -1662,7 +1835,6 @@ void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoi
graphics::MeshPointer newMesh;
entity->withReadLock([&] {
newMesh = entity->_mesh;
});
if (newMesh && newMesh->getIndexBuffer()._buffer) {
@ -1705,3 +1877,41 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) {
batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_mesh->getNumIndices(), 0);
}
QDebug operator<<(QDebug debug, PolyVoxState state) {
switch (state) {
case PolyVoxState::Ready:
debug << "Ready";
break;
case PolyVoxState::Uncompressing:
debug << "Uncompressing";
break;
case PolyVoxState::UncompressingFinished:
debug << "UncompressingFinished";
break;
case PolyVoxState::BakingMesh:
debug << "BakingMesh";
break;
case PolyVoxState::BakingMeshFinished:
debug << "BakingMeshFinished";
break;
case PolyVoxState::BakingMeshNoCompress:
debug << "BakingMeshNoCompress";
break;
case PolyVoxState::BakingMeshNoCompressFinished:
debug << "BakingMeshNoCompressFinished";
break;
case PolyVoxState::Compressing:
debug << "Compressing";
break;
case PolyVoxState::CompressingFinished:
debug << "CompressingFinished";
break;
case PolyVoxState::BakingShape:
debug << "BakingShape";
break;
case PolyVoxState::BakingShapeFinished:
debug << "BakingShapeFinished";
break;
}
return debug;
}

View file

@ -30,7 +30,25 @@
namespace render { namespace entities {
class PolyVoxEntityRenderer;
} }
} }
enum class PolyVoxState {
Ready,
Uncompressing,
UncompressingFinished,
BakingMesh,
BakingMeshFinished,
BakingMeshNoCompress,
BakingMeshNoCompressFinished,
Compressing,
CompressingFinished,
BakingShape,
BakingShapeFinished
};
QDebug operator<<(QDebug debug, PolyVoxState state);
class RenderablePolyVoxEntityItem : public PolyVoxEntityItem, public scriptable::ModelProvider {
friend class render::entities::PolyVoxEntityRenderer;
@ -113,41 +131,61 @@ public:
uint8_t getVoxelInternal(const ivec3& v) const;
bool setVoxelInternal(const ivec3& v, uint8_t toValue);
void setVoxelMarkNeighbors(int x, int y, int z, uint8_t toValue);
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); }
void compressVolumeDataFinished(const QByteArray& voxelData);
void neighborXEdgeChanged() { withWriteLock([&] { _updateFromNeighborXEdge = true; }); startUpdates(); }
void neighborYEdgeChanged() { withWriteLock([&] { _updateFromNeighborYEdge = true; }); startUpdates(); }
void neighborZEdgeChanged() { withWriteLock([&] { _updateFromNeighborZEdge = true; }); startUpdates(); }
bool getMeshes(MeshProxyList& result) override; // deprecated
virtual scriptable::ScriptableModelBase getScriptableModel() override;
virtual void update(const quint64& now) override;
bool needsToCallUpdate() const override { return _updateNeeded; }
private:
bool updateOnCount(const ivec3& v, uint8_t toValue);
PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const;
void changeUpdates(bool value);
void startUpdates();
void stopUpdates();
void recomputeMesh();
void cacheNeighbors();
void copyUpperEdgesFromNeighbors();
void bonkNeighbors();
void tellNeighborsToRecopyEdges(bool force);
bool updateDependents();
// these are run off the main thread
void decompressVolumeData();
void uncompressVolumeData();
void compressVolumeDataAndSendEditPacket();
void computeShapeInfoWorker();
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
// may not match _voxelVolumeSize.
bool _meshDirty { true }; // does collision-shape need to be recomputed?
bool _meshReady{ false };
bool _meshReady { false }; // do we have something to give scripts that ask for the mesh?
bool _voxelDataDirty { false }; // do we need to uncompress data and expand it into _volData?
bool _volDataDirty { false }; // does recomputeMesh need to be called?
bool _shapeReady { false }; // are we ready to tell bullet our shape?
PolyVoxState _state { PolyVoxState::Ready };
bool _updateNeeded { true };
graphics::MeshPointer _mesh;
ShapeInfo _shapeInfo;
std::shared_ptr<PolyVox::SimpleVolume<uint8_t>> _volData;
bool _voxelDataDirty{ true };
bool _volDataDirty { false }; // does recomputeMesh need to be called?
int _onCount; // how many non-zero voxels are in _volData
bool _neighborsNeedUpdate { false };
bool _neighborXNeedsUpdate { false };
bool _neighborYNeedsUpdate { false };
bool _neighborZNeedsUpdate { false };
bool _updateFromNeighborXEdge { false };
bool _updateFromNeighborYEdge { false };
bool _updateFromNeighborZEdge { false };
// these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID
EntityItemWeakPointer _xNNeighbor; // neighbor found by going along negative X axis
@ -156,7 +194,6 @@ private:
EntityItemWeakPointer _xPNeighbor; // neighbor found by going along positive X axis
EntityItemWeakPointer _yPNeighbor;
EntityItemWeakPointer _zPNeighbor;
};
namespace render { namespace entities {
@ -170,7 +207,7 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override {
return asTypedEntity<RenderablePolyVoxEntityItem>()->getScriptableModel();
}
protected:
virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); }
virtual ShapeKey getShapeKey() override;
@ -192,7 +229,6 @@ private:
glm::mat4 _lastVoxelToWorldMatrix;
PolyVoxEntityItem::PolyVoxSurfaceStyle _lastSurfaceStyle { PolyVoxEntityItem::SURFACE_MARCHING_CUBES };
std::array<QString, 3> _xyzTextureUrls;
bool _neighborsNeedUpdate{ false };
};
} }