diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 5118664268..c9c4c8503a 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -55,9 +55,58 @@ gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; + +/* + A PolyVoxEntity has several interdependent parts: + + _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 + + 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. + + _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. getMesh() is called to invoke the + polyVox surface extractor to create _mesh (as well as set Simulation _dirtyFlags). Because Simulation::DIRTY_SHAPE + is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on + the surface style. + + When a script changes _volData, compressVolumeDataAndSendEditPacket is called to update _voxelData and to + send a packet to the entity-server. + + decompressVolumeData, getMesh, 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 resulting mesh wont be closed -- the library assumes you'll have another polyvox next to it to continue the + mesh. + + If a polyvox entity is "edged", the voxel space is wrapped in an extra layer of zero-valued voxels. This avoids the + 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 + names have XP for x-positive, XN x-negative, etc. + + */ + + + + + EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderablePolyVoxEntityItem(entityID) }; entity->setProperties(properties); + std::static_pointer_cast(entity)->initializePolyVox(); return entity; } @@ -68,13 +117,19 @@ RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& ent _xTexture(nullptr), _yTexture(nullptr), _zTexture(nullptr) { - setVoxelVolumeSize(_voxelVolumeSize); - getMeshAsync(); } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { + withWriteLock([&] { + if (_volData) { + delete _volData; + } + }); } +void RenderablePolyVoxEntityItem::initializePolyVox() { + setVoxelVolumeSize(_voxelVolumeSize); +} bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) { switch (surfaceStyle) { @@ -88,59 +143,70 @@ bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) { return false; } - void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - _voxelDataLock.lockForWrite(); - if (_voxelData == voxelData) { - _voxelDataLock.unlock(); - return; - } - - _voxelData = voxelData; - _voxelDataDirty = true; - _voxelDataLock.unlock(); - decompressVolumeData(); + // compressed voxel information from the entity-server + withWriteLock([&] { + if (_voxelData != voxelData) { + _voxelData = voxelData; + _voxelDataDirty = true; + } + }); } - void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - if (_voxelSurfaceStyle == voxelSurfaceStyle) { - return; - } + // this controls whether the polyvox surface extractor does marching-cubes or makes a cubic mesh. It + // also determines if the extra "edged" layer is used. + bool volSizeChanged = false; - // if we are switching to or from "edged" we need to force a resize of _volData. - bool wasEdged = isEdged(_voxelSurfaceStyle); - bool willBeEdged = isEdged(voxelSurfaceStyle); - - if (wasEdged != willBeEdged) { - _volDataLock.lockForWrite(); - _volDataDirty = true; - if (_volData) { - delete _volData; + withWriteLock([&] { + if (_voxelSurfaceStyle == voxelSurfaceStyle) { + return; } - _volData = nullptr; - _voxelSurfaceStyle = voxelSurfaceStyle; - _volDataLock.unlock(); + + // if we are switching to or from "edged" we need to force a resize of _volData. + bool wasEdged = isEdged(_voxelSurfaceStyle); + bool willBeEdged = isEdged(voxelSurfaceStyle); + + if (wasEdged != willBeEdged) { + _volDataDirty = true; + if (_volData) { + delete _volData; + } + _volData = nullptr; + _voxelSurfaceStyle = voxelSurfaceStyle; + _voxelDataDirty = true; + volSizeChanged = true; + } else { + _volDataDirty = true; + _voxelSurfaceStyle = voxelSurfaceStyle; + } + }); + + if (volSizeChanged) { + // setVoxelVolumeSize will re-alloc _volData with the right size setVoxelVolumeSize(_voxelVolumeSize); - decompressVolumeData(); - } else { - _voxelSurfaceStyle = voxelSurfaceStyle; - getMesh(); } } - glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { - glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units - if (isEdged(_voxelSurfaceStyle)) { - return scale / -2.0f; - } - return scale / 2.0f; + glm::vec3 result; + withReadLock([&] { + glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units + if (isEdged(_voxelSurfaceStyle)) { + result = scale / -2.0f; + } + return scale / 2.0f; + }); + return result; } - glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { - glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units + glm::vec3 voxelVolumeSize; + withReadLock([&] { + voxelVolumeSize = _voxelVolumeSize; + }); + + glm::vec3 scale = getDimensions() / voxelVolumeSize; // meters / voxel-units bool success; // TODO -- Does this actually have to happen in world space? glm::vec3 center = getCenterPosition(success); // this handles registrationPoint changes glm::vec3 position = getPosition(success); @@ -168,18 +234,15 @@ glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { return worldToModelMatrix; } - bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { if (_locked) { return false; } - _volDataLock.lockForWrite(); - bool result = setVoxelInternal(x, y, z, toValue); - if (result) { - _volDataDirty = true; - } - _volDataLock.unlock(); + bool result = false; + withWriteLock([&] { + result = setVoxelInternal(x, y, z, toValue); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -187,6 +250,21 @@ bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) return result; } +void RenderablePolyVoxEntityItem::forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize, + std::function thunk) { + // a thread-safe way for code outside this class to iterate over a range of voxels + withReadLock([&] { + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + uint8_t uVoxelValue = getVoxelInternal(x, y, z); + thunk(x, y, z, uVoxelValue); + } + } + } + }); +} + bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { bool result = false; @@ -194,16 +272,15 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { return result; } - _volDataLock.lockForWrite(); - _volDataDirty = true; - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + result |= setVoxelInternal(x, y, z, toValue); + } } } - } - _volDataLock.unlock(); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -224,17 +301,15 @@ bool RenderablePolyVoxEntityItem::setCuboid(const glm::vec3& lowPosition, const int yHigh = std::max(std::min(yLow + (int)roundf(cuboidSize.y), (int)roundf(_voxelVolumeSize.y)), yLow); int zHigh = std::max(std::min(zLow + (int)roundf(cuboidSize.z), (int)roundf(_voxelVolumeSize.z)), zLow); - _volDataLock.lockForWrite(); - _volDataDirty = true; - - for (int x = xLow; x < xHigh; x++) { - for (int y = yLow; y < yHigh; y++) { - for (int z = zLow; z < zHigh; z++) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int x = xLow; x < xHigh; x++) { + for (int y = yLow; y < yHigh; y++) { + for (int z = zLow; z < zHigh; z++) { + result |= setVoxelInternal(x, y, z, toValue); + } } } - } - _volDataLock.unlock(); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -259,28 +334,25 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi } // This three-level for loop iterates over every voxel in the volume - _volDataLock.lockForWrite(); - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - // Store our current position as a vector... - glm::vec3 pos(x + 0.5f, y + 0.5f, z + 0.5f); // consider voxels cenetered on their coordinates - // And compute how far the current position is from the center of the volume - float fDistToCenter = glm::distance(pos, center); - // If the current voxel is less than 'radius' units from the center then we set its value - if (fDistToCenter <= radius) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + // Store our current position as a vector... + glm::vec3 pos(x + 0.5f, y + 0.5f, z + 0.5f); // consider voxels cenetered on their coordinates + // And compute how far the current position is from the center of the volume + float fDistToCenter = glm::distance(pos, center); + // If the current voxel is less than 'radius' units from the center then we set its value + if (fDistToCenter <= radius) { + result |= setVoxelInternal(x, y, z, toValue); + } } } } - } + }); if (result) { - _volDataDirty = true; - _volDataLock.unlock(); compressVolumeDataAndSendEditPacket(); - } else { - _volDataLock.unlock(); } return result; } @@ -294,30 +366,27 @@ bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float r glm::mat4 vtwMatrix = voxelToWorldMatrix(); // This three-level for loop iterates over every voxel in the volume - _volDataLock.lockForWrite(); - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - // Store our current position as a vector... - glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates - // convert to world coordinates - glm::vec3 worldPos = glm::vec3(vtwMatrix * pos); - // compute how far the current position is from the center of the volume - float fDistToCenter = glm::distance(worldPos, centerWorldCoords); - // If the current voxel is less than 'radius' units from the center then we set its value - if (fDistToCenter <= radiusWorldCoords) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + // Store our current position as a vector... + glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates + // convert to world coordinates + glm::vec3 worldPos = glm::vec3(vtwMatrix * pos); + // compute how far the current position is from the center of the volume + float fDistToCenter = glm::distance(worldPos, centerWorldCoords); + // If the current voxel is less than 'radius' units from the center then we set its value + if (fDistToCenter <= radiusWorldCoords) { + result |= setVoxelInternal(x, y, z, toValue); + } } } } - } + }); if (result) { - _volDataDirty = true; - _volDataLock.unlock(); compressVolumeDataAndSendEditPacket(); - } else { - _volDataLock.unlock(); } return result; } @@ -399,7 +468,8 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o float voxelDistance; - bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face, surfaceNormal); + bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), + voxelDistance, face, surfaceNormal); glm::vec4 voxelIntersectionPoint = glm::vec4(glm::vec3(originInVoxel) + glm::vec3(directionInVoxel) * voxelDistance, 1.0); glm::vec4 intersectionPoint = vtwMatrix * voxelIntersectionPoint; @@ -414,13 +484,15 @@ PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originIn PolyVox::Vector3DFloat startPoint(originInVoxel.x, originInVoxel.y, originInVoxel.z); PolyVox::Vector3DFloat endPoint(farInVoxel.x, farInVoxel.y, farInVoxel.z); - _volDataLock.lockForRead(); - RaycastFunctor callback(_volData); - PolyVox::RaycastResult raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); - _volDataLock.unlock(); + PolyVox::RaycastResult raycastResult; + withReadLock([&] { + RaycastFunctor callback(_volData); + raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); + + // result is in voxel-space coordinates. + result = callback._result; + }); - // result is in voxel-space coordinates. - result = callback._result; return raycastResult; } @@ -438,19 +510,22 @@ void RenderablePolyVoxEntityItem::updateRegistrationPoint(const glm::vec3& value } bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { - _meshLock.lockForRead(); - if (_meshDirty) { - _meshLock.unlock(); + // 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) { + _meshDirty = false; computeShapeInfoWorker(); return false; } - _meshLock.unlock(); return true; } void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { - QReadLocker(&this->_shapeInfoLock); - info = _shapeInfo; + // the shape was actually computed in isReadyToComputeShape. Just hand it off, here. + withWriteLock([&] { + info = _shapeInfo; + }); } void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { @@ -479,18 +554,29 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::PolyVox); Q_ASSERT(args->_batch); - _volDataLock.lockForRead(); - if (_volDataDirty) { - _volDataLock.unlock(); + bool voxelDataDirty; + bool volDataDirty; + withWriteLock([&] { + voxelDataDirty = _voxelDataDirty; + volDataDirty = _volDataDirty; + if (_voxelDataDirty) { + _voxelDataDirty = false; + } else if (_volDataDirty) { + _volDataDirty = false; + } + }); + if (voxelDataDirty) { + decompressVolumeData(); + } else if (volDataDirty) { getMesh(); - } else { - _volDataLock.unlock(); } - - _meshLock.lockForRead(); - model::MeshPointer mesh = _mesh; - _meshLock.unlock(); + model::MeshPointer mesh; + glm::vec3 voxelVolumeSize; + withReadLock([&] { + mesh = _mesh; + voxelVolumeSize = _voxelVolumeSize; + }); if (!_pipeline) { gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(std::string(polyvox_vert)); @@ -552,7 +638,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { } int voxelVolumeSizeLocation = _pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize"); - batch._glUniform3f(voxelVolumeSizeLocation, _voxelVolumeSize.x, _voxelVolumeSize.y, _voxelVolumeSize.z); + batch._glUniform3f(voxelVolumeSizeLocation, voxelVolumeSize.x, voxelVolumeSize.y, voxelVolumeSize.z); batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)mesh->getNumIndices(), 0); } @@ -636,49 +722,52 @@ glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& local void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - if (_volData && _voxelVolumeSize == voxelVolumeSize) { - return; - } + // This controls how many individual voxels are in the entity. This is unrelated to + // the dimentions of the entity -- it defines the size of the arrays that hold voxel values. + // In addition to setting the number of voxels, this is used in a few places for its + // side-effect of allocating _volData to be the correct size. + withWriteLock([&] { + if (_volData && _voxelVolumeSize == voxelVolumeSize) { + return; + } - _volDataLock.lockForWrite(); - _volDataDirty = true; - _voxelVolumeSize = voxelVolumeSize; + _voxelDataDirty = true; + _voxelVolumeSize = voxelVolumeSize; - if (_volData) { - delete _volData; - } - _onCount = 0; + if (_volData) { + delete _volData; + } + _onCount = 0; - if (isEdged(_voxelSurfaceStyle)) { - // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This - // changes how the surface extractor acts -- mainly it becomes impossible to have holes in the - // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the - // voxel space. - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x + 1, // corners are inclusive - _voxelVolumeSize.y + 1, - _voxelVolumeSize.z + 1); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } else { - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - // these should each have -1 after them, but if we leave layers on the upper-axis faces, - // they act more like I expect. - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x, - _voxelVolumeSize.y, - _voxelVolumeSize.z); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } + if (isEdged(_voxelSurfaceStyle)) { + // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This + // changes how the surface extractor acts -- it becomes impossible to have holes in the + // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the + // voxel space. + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x + 1, // corners are inclusive + _voxelVolumeSize.y + 1, + _voxelVolumeSize.z + 1); + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); + } else { + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + // these should each have -1 after them, but if we leave layers on the upper-axis faces, + // they act more like I expect. + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x, + _voxelVolumeSize.y, + _voxelVolumeSize.z); + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); + } - // having the "outside of voxel-space" value be 255 has helped me notice some problems. - _volData->setBorderValue(255); - _volDataLock.unlock(); - decompressVolumeData(); + // having the "outside of voxel-space" value be 255 has helped me notice some problems. + _volData->setBorderValue(255); + }); } -bool RenderablePolyVoxEntityItem::inUserBounds(const PolyVox::SimpleVolume* vol, - PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z) const { +bool inUserBounds(const PolyVox::SimpleVolume* vol, + PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z) { // x, y, z are in user voxel-coords, not adjusted-for-edge voxel-coords. if (isEdged(surfaceStyle)) { if (x < 0 || y < 0 || z < 0 || @@ -697,8 +786,11 @@ bool RenderablePolyVoxEntityItem::inUserBounds(const PolyVox::SimpleVolume_volDataLock); - return getVoxelInternal(x, y, z); + uint8_t result; + withReadLock([&] { + result = getVoxelInternal(x, y, z); + }); + return result; } @@ -718,7 +810,8 @@ uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) { bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { - // set a voxel without recompressing the voxel data + // 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, x, y, z)) { return result; @@ -732,6 +825,12 @@ bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t _volData->setVoxelAt(x, y, z, toValue); } + if (x == 0 || y == 0 || z == 0) { + _neighborsNeedUpdate = true; + } + + _volDataDirty |= result; + return result; } @@ -760,214 +859,176 @@ bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toV } void RenderablePolyVoxEntityItem::decompressVolumeData() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::decompressVolumeDataAsync); + // take compressed data and expand it into _volData. + QByteArray voxelData; + auto entity = std::static_pointer_cast(getThisPointer()); + + withReadLock([&] { + voxelData = _voxelData; + }); + + QtConcurrent::run([=] { + QDataStream reader(voxelData); + quint16 voxelXSize, voxelYSize, voxelZSize; + reader >> voxelXSize; + reader >> voxelYSize; + reader >> voxelZSize; + + if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || + voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || + voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { + qDebug() << "voxelSize is not reasonable, skipping decompressions." + << voxelXSize << voxelYSize << voxelZSize << getName() << getID(); + entity->setVoxelDataDirty(false); + return; + } + int rawSize = voxelXSize * voxelYSize * voxelZSize; + + QByteArray compressedData; + reader >> compressedData; + + QByteArray uncompressedData = qUncompress(compressedData); + + if (uncompressedData.size() != rawSize) { + qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" + << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size() + << getName() << getID(); + entity->setVoxelDataDirty(false); + return; + } + + entity->setVoxelsFromData(uncompressedData, voxelXSize, voxelYSize, voxelZSize); + }); } -// take compressed data and expand it into _volData. -void RenderablePolyVoxEntityItem::decompressVolumeDataAsync() { - _voxelDataLock.lockForRead(); - QDataStream reader(_voxelData); - quint16 voxelXSize, voxelYSize, voxelZSize; - reader >> voxelXSize; - reader >> voxelYSize; - reader >> voxelZSize; - - if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || - voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || - voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { - qDebug() << "voxelSize is not reasonable, skipping decompressions." - << voxelXSize << voxelYSize << voxelZSize << getName() << getID(); - _voxelDataDirty = false; - _voxelDataLock.unlock(); - _threadRunning.release(); - return; - } - - int rawSize = voxelXSize * voxelYSize * voxelZSize; - - QByteArray compressedData; - reader >> compressedData; - _voxelDataDirty = false; - _voxelDataLock.unlock(); - QByteArray uncompressedData = qUncompress(compressedData); - - if (uncompressedData.size() != rawSize) { - qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" - << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size() - << getName() << getID(); - _threadRunning.release(); - return; - } - - _volDataLock.lockForWrite(); - if (!_volData) { - _volDataLock.unlock(); - _threadRunning.release(); - return; - } - _volDataDirty = true; - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; - setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); +void RenderablePolyVoxEntityItem::setVoxelsFromData(QByteArray uncompressedData, + quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) { + // this accepts the payload from decompressVolumeData + withWriteLock([&] { + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; + setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); + } } } - } - _volDataLock.unlock(); - _threadRunning.release(); + _volDataDirty = true; + }); } void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync); -} + // compress the data in _volData and save the results. The compressed form is used during + // saves to disk and for transmission over the wire to the entity-server -// compress the data in _volData and save the results. The compressed form is used during -// saves to disk and for transmission over the wire -void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync() { - quint16 voxelXSize = _voxelVolumeSize.x; - quint16 voxelYSize = _voxelVolumeSize.y; - quint16 voxelZSize = _voxelVolumeSize.z; - int rawSize = voxelXSize * voxelYSize * voxelZSize; + EntityItemPointer entity = getThisPointer(); - QByteArray uncompressedData = QByteArray(rawSize, '\0'); - - _volDataLock.lockForRead(); - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - uint8_t uVoxelValue = getVoxelInternal(x, y, z); - int uncompressedIndex = - z * voxelYSize * voxelXSize + - y * voxelXSize + - x; - uncompressedData[uncompressedIndex] = uVoxelValue; - } - } - } - _volDataLock.unlock(); - - QByteArray newVoxelData; - QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); - - writer << voxelXSize << voxelYSize << voxelZSize; - - QByteArray compressedData = qCompress(uncompressedData, 9); - writer << compressedData; - - // make sure the compressed data can be sent over the wire-protocol - if (newVoxelData.size() > 1150) { - // 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. - // XXX - qDebug() << "compressed voxel data is too large" << getName() << getID(); - _threadRunning.release(); - return; - } - - auto now = usecTimestampNow(); - setLastEdited(now); - setLastBroadcast(now); - - _voxelDataLock.lockForWrite(); - _voxelDataDirty = true; - _voxelData = newVoxelData; - _voxelDataLock.unlock(); - - EntityItemProperties properties = getProperties(); - properties.setVoxelDataDirty(); - properties.setLastEdited(now); + quint16 voxelXSize; + quint16 voxelYSize; + quint16 voxelZSize; + withReadLock([&] { + voxelXSize = _voxelVolumeSize.x; + voxelYSize = _voxelVolumeSize.y; + voxelZSize = _voxelVolumeSize.z; + }); EntityTreeElementPointer element = getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; - EntitySimulation* simulation = tree ? tree->getSimulation() : nullptr; - PhysicalEntitySimulation* peSimulation = static_cast(simulation); - EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; - if (packetSender) { - packetSender->queueEditEntityMessage(PacketType::EntityEdit, _id, properties); - } - _threadRunning.release(); + + QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] { + int rawSize = voxelXSize * voxelYSize * voxelZSize; + QByteArray uncompressedData = QByteArray(rawSize, '\0'); + + auto polyVoxEntity = std::static_pointer_cast(entity); + polyVoxEntity->forEachVoxelValue(voxelXSize, voxelYSize, voxelZSize, [&] (int x, int y, int z, uint8_t uVoxelValue) { + int uncompressedIndex = + z * voxelYSize * voxelXSize + + y * voxelXSize + + x; + uncompressedData[uncompressedIndex] = uVoxelValue; + }); + + QByteArray newVoxelData; + QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); + + writer << voxelXSize << voxelYSize << voxelZSize; + + QByteArray compressedData = qCompress(uncompressedData, 9); + writer << compressedData; + + // make sure the compressed data can be sent over the wire-protocol + if (newVoxelData.size() > 1150) { + // 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. + qDebug() << "compressed voxel data is too large" << entity->getName() << entity->getID(); + return; + } + + auto now = usecTimestampNow(); + entity->setLastEdited(now); + entity->setLastBroadcast(now); + + std::static_pointer_cast(entity)->setVoxelData(newVoxelData); + + tree->withReadLock([&] { + EntityItemProperties properties = entity->getProperties(); + properties.setVoxelDataDirty(); + properties.setLastEdited(now); + + EntitySimulation* simulation = tree ? tree->getSimulation() : nullptr; + PhysicalEntitySimulation* peSimulation = static_cast(simulation); + EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; + if (packetSender) { + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + } + }); + }); } -void RenderablePolyVoxEntityItem::getMesh() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::getMeshAsync); -} +EntityItemPointer lookUpNeighbor(EntityTreePointer tree, EntityItemID neighborID, EntityItemWeakPointer& currentWP) { + EntityItemPointer current = currentWP.lock(); -void RenderablePolyVoxEntityItem::clearOutOfDateNeighbors() { - if (_xNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); - if (currentXNNeighbor && currentXNNeighbor->getID() != _xNNeighborID) { - _xNNeighbor.reset(); - } - } - if (_yNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentYNNeighbor = _yNNeighbor.lock(); - if (currentYNNeighbor && currentYNNeighbor->getID() != _yNNeighborID) { - _yNNeighbor.reset(); - } - } - if (_zNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentZNNeighbor = _zNNeighbor.lock(); - if (currentZNNeighbor && currentZNNeighbor->getID() != _zNNeighborID) { - _zNNeighbor.reset(); - } + if (!current && neighborID == UNKNOWN_ENTITY_ID) { + // no neighbor + return nullptr; } - if (_xPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentXPNeighbor = _xPNeighbor.lock(); - if (currentXPNeighbor && currentXPNeighbor->getID() != _xPNeighborID) { - _xPNeighbor.reset(); - } - } - if (_yPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentYPNeighbor = _yPNeighbor.lock(); - if (currentYPNeighbor && currentYPNeighbor->getID() != _yPNeighborID) { - _yPNeighbor.reset(); - } - } - if (_zPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentZPNeighbor = _zPNeighbor.lock(); - if (currentZPNeighbor && currentZPNeighbor->getID() != _zPNeighborID) { - _zPNeighbor.reset(); - } + if (current && current->getID() == neighborID) { + // same neighbor + return current; } + if (neighborID == UNKNOWN_ENTITY_ID) { + currentWP.reset(); + return nullptr; + } + + current = tree->findEntityByID(neighborID); + if (!current) { + return nullptr; + } + + currentWP = current; + return current; } void RenderablePolyVoxEntityItem::cacheNeighbors() { - clearOutOfDateNeighbors(); + // this attempts to turn neighbor entityIDs into neighbor weak-pointers EntityTreeElementPointer element = getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; if (!tree) { return; } - - if (_xNNeighborID != UNKNOWN_ENTITY_ID && _xNNeighbor.expired()) { - _xNNeighbor = tree->findEntityByID(_xNNeighborID); - } - if (_yNNeighborID != UNKNOWN_ENTITY_ID && _yNNeighbor.expired()) { - _yNNeighbor = tree->findEntityByID(_yNNeighborID); - } - if (_zNNeighborID != UNKNOWN_ENTITY_ID && _zNNeighbor.expired()) { - _zNNeighbor = tree->findEntityByID(_zNNeighborID); - } - - if (_xPNeighborID != UNKNOWN_ENTITY_ID && _xPNeighbor.expired()) { - _xPNeighbor = tree->findEntityByID(_xPNeighborID); - } - if (_yPNeighborID != UNKNOWN_ENTITY_ID && _yPNeighbor.expired()) { - _yPNeighbor = tree->findEntityByID(_yPNeighborID); - } - if (_zPNeighborID != UNKNOWN_ENTITY_ID && _zPNeighbor.expired()) { - _zPNeighbor = tree->findEntityByID(_zPNeighborID); - } - + lookUpNeighbor(tree, _xNNeighborID, _xNNeighbor); + lookUpNeighbor(tree, _yNNeighborID, _yNNeighbor); + lookUpNeighbor(tree, _zNNeighborID, _zNNeighbor); + lookUpNeighbor(tree, _xPNeighborID, _xPNeighbor); + lookUpNeighbor(tree, _yPNeighborID, _yPNeighbor); + lookUpNeighbor(tree, _zPNeighborID, _zPNeighbor); } void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() { + // fill in our upper edges with a copy of our neighbors lower edges so that the meshes knit together if (_voxelSurfaceStyle != PolyVoxEntityItem::SURFACE_MARCHING_CUBES) { return; } @@ -979,347 +1040,360 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() { if (currentXPNeighbor) { auto polyVoxXPNeighbor = std::dynamic_pointer_cast(currentXPNeighbor); if (polyVoxXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int y = 0; y < _volData->getHeight(); y++) { - for (int z = 0; z < _volData->getDepth(); z++) { - uint8_t neighborValue = polyVoxXPNeighbor->getVoxel(0, y, z); - _volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue); + withWriteLock([&] { + for (int y = 0; y < _volData->getHeight(); y++) { + for (int z = 0; z < _volData->getDepth(); z++) { + uint8_t neighborValue = polyVoxXPNeighbor->getVoxel(0, y, z); + _volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue); + } } - } + }); } } if (currentYPNeighbor) { auto polyVoxYPNeighbor = std::dynamic_pointer_cast(currentYPNeighbor); if (polyVoxYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int x = 0; x < _volData->getWidth(); x++) { - for (int z = 0; z < _volData->getDepth(); z++) { - uint8_t neighborValue = polyVoxYPNeighbor->getVoxel(x, 0, z); - _volData->setVoxelAt(x, _volData->getWidth() - 1, z, neighborValue); + withWriteLock([&] { + for (int x = 0; x < _volData->getWidth(); x++) { + for (int z = 0; z < _volData->getDepth(); z++) { + uint8_t neighborValue = polyVoxYPNeighbor->getVoxel(x, 0, z); + _volData->setVoxelAt(x, _volData->getWidth() - 1, z, neighborValue); + } } - } + }); } } if (currentZPNeighbor) { auto polyVoxZPNeighbor = std::dynamic_pointer_cast(currentZPNeighbor); if (polyVoxZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int x = 0; x < _volData->getWidth(); x++) { - for (int y = 0; y < _volData->getHeight(); y++) { - uint8_t neighborValue = polyVoxZPNeighbor->getVoxel(x, y, 0); - _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); + withWriteLock([&] { + for (int x = 0; x < _volData->getWidth(); x++) { + for (int y = 0; y < _volData->getHeight(); y++) { + uint8_t neighborValue = polyVoxZPNeighbor->getVoxel(x, y, 0); + _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); + } } - } + }); } } } -void RenderablePolyVoxEntityItem::getMeshAsync() { - model::MeshPointer mesh(new model::Mesh()); +void RenderablePolyVoxEntityItem::getMesh() { + // use _volData to make a renderable mesh + PolyVoxSurfaceStyle voxelSurfaceStyle; + withReadLock([&] { + voxelSurfaceStyle = _voxelSurfaceStyle; + }); cacheNeighbors(); - - // A mesh object to hold the result of surface extraction - PolyVox::SurfaceMesh polyVoxMesh; - - _volDataLock.lockForRead(); - if (!_volData) { - _volDataLock.unlock(); - return; - } copyUpperEdgesFromNeighbors(); - switch (_voxelSurfaceStyle) { - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; + auto entity = std::static_pointer_cast(getThisPointer()); + + + QtConcurrent::run([entity, voxelSurfaceStyle] { + model::MeshPointer mesh(new model::Mesh()); + + // A mesh object to hold the result of surface extraction + PolyVox::SurfaceMesh polyVoxMesh; + + entity->withReadLock([&] { + PolyVox::SimpleVolume* volData = entity->getVolData(); + switch (voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: { + PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_CUBIC: { + PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + } + }); + + // convert PolyVox mesh to a Sam mesh + const std::vector& vecIndices = polyVoxMesh.getIndices(); + auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), + (gpu::Byte*)vecIndices.data()); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); + mesh->setIndexBuffer(*indexBufferView); + + const std::vector& vecVertices = polyVoxMesh.getVertices(); + auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), + (gpu::Byte*)vecVertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + gpu::Resource::Size vertexBufferSize = 0; + if (vertexBufferPtr->getSize() > sizeof(float) * 3) { + vertexBufferSize = vertexBufferPtr->getSize() - sizeof(float) * 3; } + auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, 0, vertexBufferSize, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); + mesh->setVertexBuffer(*vertexBufferView); + mesh->addAttribute(gpu::Stream::NORMAL, + gpu::BufferView(vertexBufferPtr, + sizeof(float) * 3, + vertexBufferPtr->getSize() - sizeof(float) * 3, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); + entity->setMesh(mesh); + }); +} + +void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) { + // this catches the payload from getMesh + bool neighborsNeedUpdate; + withWriteLock([&] { + _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; + _mesh = mesh; + _meshDirty = true; + _meshInitialized = true; + neighborsNeedUpdate = _neighborsNeedUpdate; + _neighborsNeedUpdate = false; + }); + if (neighborsNeedUpdate) { + bonkNeighbors(); } - - // convert PolyVox mesh to a Sam mesh - const std::vector& vecIndices = polyVoxMesh.getIndices(); - auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), - (gpu::Byte*)vecIndices.data()); - auto indexBufferPtr = gpu::BufferPointer(indexBuffer); - auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); - mesh->setIndexBuffer(*indexBufferView); - - const std::vector& vecVertices = polyVoxMesh.getVertices(); - auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), - (gpu::Byte*)vecVertices.data()); - auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - gpu::Resource::Size vertexBufferSize = 0; - if (vertexBufferPtr->getSize() > sizeof(float) * 3) { - vertexBufferSize = vertexBufferPtr->getSize() - sizeof(float) * 3; - } - auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, 0, vertexBufferSize, sizeof(PolyVox::PositionMaterialNormal), - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); - mesh->setVertexBuffer(*vertexBufferView); - mesh->addAttribute(gpu::Stream::NORMAL, - gpu::BufferView(vertexBufferPtr, - sizeof(float) * 3, - vertexBufferPtr->getSize() - sizeof(float) * 3, - sizeof(PolyVox::PositionMaterialNormal), - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); - - _meshLock.lockForWrite(); - _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; - _mesh = mesh; - _meshDirty = true; - _meshLock.unlock(); - _volDataDirty = false; - _volDataLock.unlock(); - bonkNeighbors(); - _threadRunning.release(); } void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync); -} - - -void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() { - QVector> points; - AABox box; - glm::mat4 vtoM = voxelToLocalMatrix(); - - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || - _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { - // pull each triangle in the mesh into a polyhedron which can be collided with - unsigned int i = 0; - - _meshLock.lockForRead(); - model::MeshPointer mesh = _mesh; - const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); - const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); - _meshLock.unlock(); - - gpu::BufferView::Iterator it = indexBufferView.cbegin(); - while (it != indexBufferView.cend()) { - uint32_t p0Index = *(it++); - uint32_t p1Index = *(it++); - uint32_t p2Index = *(it++); - - const glm::vec3& p0 = vertexBufferView.get(p0Index); - const glm::vec3& p1 = vertexBufferView.get(p1Index); - const glm::vec3& p2 = vertexBufferView.get(p2Index); - - glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face - glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); - glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; - - glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); - glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); - glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); - glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); - - box += p0Model; - box += p1Model; - box += p2Model; - box += p3Model; - - QVector pointsInPart; - pointsInPart << p0Model; - pointsInPart << p1Model; - pointsInPart << p2Model; - pointsInPart << p3Model; - // add next convex hull - QVector newMeshPoints; - points << newMeshPoints; - // add points to the new convex hull - points[i++] << pointsInPart; - } - } else { - unsigned int i = 0; - - _volDataLock.lockForRead(); - if (!_volData) { - _volDataLock.unlock(); - return; - } - - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - if (getVoxelInternal(x, y, z) > 0) { - - if ((x > 0 && getVoxel(x - 1, y, z) > 0) && - (y > 0 && getVoxel(x, y - 1, z) > 0) && - (z > 0 && getVoxel(x, y, z - 1) > 0) && - (x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) > 0) && - (y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) > 0) && - (z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) > 0)) { - // this voxel has neighbors in every cardinal direction, so there's no need - // to include it in the collision hull. - continue; - } - - QVector pointsInPart; - - float offL = -0.5f; - float offH = 0.5f; - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { - offL += 1.0f; - offH += 1.0f; - } - - glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); - glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); - glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); - glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); - glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); - glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); - glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); - glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); - - box += p000; - box += p001; - box += p010; - box += p011; - box += p100; - box += p101; - box += p110; - box += p111; - - pointsInPart << p000; - pointsInPart << p001; - pointsInPart << p010; - pointsInPart << p011; - pointsInPart << p100; - pointsInPart << p101; - pointsInPart << p110; - pointsInPart << p111; - - // add next convex hull - QVector newMeshPoints; - points << newMeshPoints; - // add points to the new convex hull - points[i++] << pointsInPart; - } - } - } - } - _volDataLock.unlock(); + // 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 (!_meshInitialized) { + return; } + EntityItemPointer entity = getThisPointer(); + + PolyVoxSurfaceStyle voxelSurfaceStyle; + glm::vec3 voxelVolumeSize; + model::MeshPointer mesh; + + withReadLock([&] { + voxelSurfaceStyle = _voxelSurfaceStyle; + voxelVolumeSize = _voxelVolumeSize; + mesh = _mesh; + }); + + QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { + auto polyVoxEntity = std::static_pointer_cast(entity); + QVector> points; + AABox box; + glm::mat4 vtoM = std::static_pointer_cast(entity)->voxelToLocalMatrix(); + + if (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || + voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + // pull each triangle in the mesh into a polyhedron which can be collided with + unsigned int i = 0; + + const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); + const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); + + gpu::BufferView::Iterator it = indexBufferView.cbegin(); + while (it != indexBufferView.cend()) { + uint32_t p0Index = *(it++); + uint32_t p1Index = *(it++); + uint32_t p2Index = *(it++); + + const glm::vec3& p0 = vertexBufferView.get(p0Index); + const glm::vec3& p1 = vertexBufferView.get(p1Index); + const glm::vec3& p2 = vertexBufferView.get(p2Index); + + glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face + glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); + glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; + + glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); + glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); + glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); + glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); + + box += p0Model; + box += p1Model; + box += p2Model; + box += p3Model; + + QVector pointsInPart; + pointsInPart << p0Model; + pointsInPart << p1Model; + pointsInPart << p2Model; + pointsInPart << p3Model; + // add next convex hull + QVector newMeshPoints; + points << newMeshPoints; + // add points to the new convex hull + points[i++] << pointsInPart; + } + } else { + unsigned int i = 0; + polyVoxEntity->forEachVoxelValue(voxelVolumeSize.x, voxelVolumeSize.y, voxelVolumeSize.z, + [&](int x, int y, int z, uint8_t value) { + if (value > 0) { + if ((x > 0 && polyVoxEntity->getVoxelInternal(x - 1, y, z) > 0) && + (y > 0 && polyVoxEntity->getVoxelInternal(x, y - 1, z) > 0) && + (z > 0 && polyVoxEntity->getVoxelInternal(x, y, z - 1) > 0) && + (x < voxelVolumeSize.x - 1 && polyVoxEntity->getVoxelInternal(x + 1, y, z) > 0) && + (y < voxelVolumeSize.y - 1 && polyVoxEntity->getVoxelInternal(x, y + 1, z) > 0) && + (z < voxelVolumeSize.z - 1 && polyVoxEntity->getVoxelInternal(x, y, z + 1) > 0)) { + // this voxel has neighbors in every cardinal direction, so there's no need + // to include it in the collision hull. + return; + } + + QVector pointsInPart; + + float offL = -0.5f; + float offH = 0.5f; + if (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { + offL += 1.0f; + offH += 1.0f; + } + + glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); + glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); + glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); + glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); + glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); + glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); + glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); + glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); + + box += p000; + box += p001; + box += p010; + box += p011; + box += p100; + box += p101; + box += p110; + box += p111; + + pointsInPart << p000; + pointsInPart << p001; + pointsInPart << p010; + pointsInPart << p011; + pointsInPart << p100; + pointsInPart << p101; + pointsInPart << p110; + pointsInPart << p111; + + // add next convex hull + QVector newMeshPoints; + points << newMeshPoints; + // add points to the new convex hull + points[i++] << pointsInPart; + } + }); + } + polyVoxEntity->setCollisionPoints(points, box); + }); +} + +void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector> points, AABox box) { + // this catches the payload from computeShapeInfoWorker if (points.isEmpty()) { - _shapeInfoLock.lockForWrite(); EntityItem::computeShapeInfo(_shapeInfo); - _shapeInfoLock.unlock(); - _threadRunning.release(); return; } glm::vec3 collisionModelDimensions = box.getDimensions(); // include the registrationPoint in the shape key, because the offset is already // included in the points and the shapeManager wont know that the shape has changed. - QString shapeKey = QString(_voxelData.toBase64()) + "," + - QString::number(_registrationPoint.x) + "," + - QString::number(_registrationPoint.y) + "," + - QString::number(_registrationPoint.z); - _shapeInfoLock.lockForWrite(); - _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey); - _shapeInfo.setConvexHulls(points); - // adjustShapeInfoByRegistration(_shapeInfo); - _shapeInfoLock.unlock(); - - _meshLock.lockForWrite(); - _meshDirty = false; - _meshLock.unlock(); - _threadRunning.release(); - return; + withWriteLock([&] { + QString shapeKey = QString(_voxelData.toBase64()) + "," + + QString::number(_registrationPoint.x) + "," + + QString::number(_registrationPoint.y) + "," + + QString::number(_registrationPoint.z); + _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey); + _shapeInfo.setConvexHulls(points); + _meshDirty = false; + }); } - void RenderablePolyVoxEntityItem::setXNNeighborID(const EntityItemID& xNNeighborID) { + if (xNNeighborID == _id) { // TODO loops are still possible + return; + } + if (xNNeighborID != _xNNeighborID) { PolyVoxEntityItem::setXNNeighborID(xNNeighborID); cacheNeighbors(); - EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); - if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxXNNeighbor = std::dynamic_pointer_cast(currentXNNeighbor); - polyVoxXNNeighbor->setXPNeighborID(_id); - polyVoxXNNeighbor->rebakeMesh(); - } } } void RenderablePolyVoxEntityItem::setYNNeighborID(const EntityItemID& yNNeighborID) { + if (yNNeighborID == _id) { // TODO loops are still possible + return; + } + if (yNNeighborID != _yNNeighborID) { PolyVoxEntityItem::setYNNeighborID(yNNeighborID); cacheNeighbors(); - EntityItemPointer currentYNNeighbor = _yNNeighbor.lock(); - if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxYNNeighbor = std::dynamic_pointer_cast(currentYNNeighbor); - polyVoxYNNeighbor->setYPNeighborID(_id); - polyVoxYNNeighbor->rebakeMesh(); - } } } void RenderablePolyVoxEntityItem::setZNNeighborID(const EntityItemID& zNNeighborID) { + if (zNNeighborID == _id) { // TODO loops are still possible + return; + } + if (zNNeighborID != _zNNeighborID) { PolyVoxEntityItem::setZNNeighborID(zNNeighborID); cacheNeighbors(); - EntityItemPointer currentZNNeighbor = _yNNeighbor.lock(); - if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxZNNeighbor = std::dynamic_pointer_cast(currentZNNeighbor); - polyVoxZNNeighbor->setZPNeighborID(_id); - polyVoxZNNeighbor->rebakeMesh(); - } } } - void RenderablePolyVoxEntityItem::setXPNeighborID(const EntityItemID& xPNeighborID) { + if (xPNeighborID == _id) { // TODO loops are still possible + return; + } if (xPNeighborID != _xPNeighborID) { PolyVoxEntityItem::setXPNeighborID(xPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } void RenderablePolyVoxEntityItem::setYPNeighborID(const EntityItemID& yPNeighborID) { + if (yPNeighborID == _id) { // TODO loops are still possible + return; + } if (yPNeighborID != _yPNeighborID) { PolyVoxEntityItem::setYPNeighborID(yPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } void RenderablePolyVoxEntityItem::setZPNeighborID(const EntityItemID& zPNeighborID) { + if (zPNeighborID == _id) { // TODO loops are still possible + return; + } if (zPNeighborID != _zPNeighborID) { PolyVoxEntityItem::setZPNeighborID(zPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } -void RenderablePolyVoxEntityItem::rebakeMesh() { - QReadLocker(&this->_volDataLock); - _volDataDirty = true; -} - void RenderablePolyVoxEntityItem::bonkNeighbors() { - clearOutOfDateNeighbors(); + // flag neighbors to the negative of this entity as needing to rebake their meshes. cacheNeighbors(); EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); @@ -1328,14 +1402,14 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxXNNeighbor = std::dynamic_pointer_cast(currentXNNeighbor); - polyVoxXNNeighbor->rebakeMesh(); + polyVoxXNNeighbor->setVolDataDirty(); } if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxYNNeighbor = std::dynamic_pointer_cast(currentYNNeighbor); - polyVoxYNNeighbor->rebakeMesh(); + polyVoxYNNeighbor->setVolDataDirty(); } if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxZNNeighbor = std::dynamic_pointer_cast(currentZNNeighbor); - polyVoxZNNeighbor->rebakeMesh(); + polyVoxZNNeighbor->setVolDataDirty(); } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index b40507f36a..e5afb94afa 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -48,6 +48,8 @@ public: virtual ~RenderablePolyVoxEntityItem(); + void initializePolyVox(); + virtual void somethingChangedNotification() { // This gets called from EnityItem::readEntityDataFromBuffer every time a packet describing // this entity comes from the entity-server. It gets called even if nothing has actually changed @@ -114,17 +116,28 @@ public: virtual void setYPNeighborID(const EntityItemID& yPNeighborID); virtual void setZPNeighborID(const EntityItemID& zPNeighborID); - virtual void rebakeMesh(); - virtual void updateRegistrationPoint(const glm::vec3& value); + void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize); + void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize, + std::function thunk); + + void setMesh(model::MeshPointer mesh); + void setCollisionPoints(const QVector> points, AABox box); + PolyVox::SimpleVolume* getVolData() { return _volData; } + + uint8_t getVoxelInternal(int x, int y, int z); + bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + + void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); } + private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. model::MeshPointer _mesh; - bool _meshDirty; // does collision-shape need to be recomputed? - mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive}; + bool _meshDirty { true }; // does collision-shape need to be recomputed? + bool _meshInitialized { false }; NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; @@ -135,44 +148,35 @@ private: static gpu::PipelinePointer _pipeline; ShapeInfo _shapeInfo; - mutable QReadWriteLock _shapeInfoLock; PolyVox::SimpleVolume* _volData = nullptr; - mutable QReadWriteLock _volDataLock{QReadWriteLock::Recursive}; // lock for _volData bool _volDataDirty = false; // does getMesh need to be called? int _onCount; // how many non-zero voxels are in _volData - bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z) const; - uint8_t getVoxelInternal(int x, int y, int z); - bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + bool _neighborsNeedUpdate { false }; + bool updateOnCount(int x, int y, int z, uint8_t toValue); PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; // these are run off the main thread void decompressVolumeData(); - void decompressVolumeDataAsync(); void compressVolumeDataAndSendEditPacket(); - void compressVolumeDataAndSendEditPacketAsync(); - void getMesh(); - void getMeshAsync(); + virtual void getMesh(); // recompute mesh void computeShapeInfoWorker(); - void computeShapeInfoWorkerAsync(); - - QSemaphore _threadRunning{1}; // these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID - EntityItemWeakPointer _xNNeighbor; // neighor found by going along negative X axis + EntityItemWeakPointer _xNNeighbor; // neighbor found by going along negative X axis EntityItemWeakPointer _yNNeighbor; EntityItemWeakPointer _zNNeighbor; - EntityItemWeakPointer _xPNeighbor; // neighor found by going along positive X axis + EntityItemWeakPointer _xPNeighbor; // neighbor found by going along positive X axis EntityItemWeakPointer _yPNeighbor; EntityItemWeakPointer _zPNeighbor; - void clearOutOfDateNeighbors(); void cacheNeighbors(); void copyUpperEdgesFromNeighbors(); void bonkNeighbors(); }; +bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z); #endif // hifi_RenderablePolyVoxEntityItem_h diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 9b85938a78..44bf940dae 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -64,44 +64,47 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID) : } void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - QWriteLocker(&this->_voxelDataLock); + withWriteLock([&] { + assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x); + assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); + assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); - assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x); - assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); - assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); + _voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z)); + if (_voxelVolumeSize.x < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1"; + _voxelVolumeSize.x = 1; + } + if (_voxelVolumeSize.x > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to max"; + _voxelVolumeSize.x = MAX_VOXEL_DIMENSION; + } - _voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z)); - if (_voxelVolumeSize.x < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1"; - _voxelVolumeSize.x = 1; - } - if (_voxelVolumeSize.x > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to max"; - _voxelVolumeSize.x = MAX_VOXEL_DIMENSION; - } + if (_voxelVolumeSize.y < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to 1"; + _voxelVolumeSize.y = 1; + } + if (_voxelVolumeSize.y > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to max"; + _voxelVolumeSize.y = MAX_VOXEL_DIMENSION; + } - if (_voxelVolumeSize.y < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to 1"; - _voxelVolumeSize.y = 1; - } - if (_voxelVolumeSize.y > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to max"; - _voxelVolumeSize.y = MAX_VOXEL_DIMENSION; - } - - if (_voxelVolumeSize.z < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to 1"; - _voxelVolumeSize.z = 1; - } - if (_voxelVolumeSize.z > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max"; - _voxelVolumeSize.z = MAX_VOXEL_DIMENSION; - } + if (_voxelVolumeSize.z < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to 1"; + _voxelVolumeSize.z = 1; + } + if (_voxelVolumeSize.z > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max"; + _voxelVolumeSize.z = MAX_VOXEL_DIMENSION; + } + }); } -const glm::vec3& PolyVoxEntityItem::getVoxelVolumeSize() const { - QWriteLocker locker(&this->_voxelDataLock); - return _voxelVolumeSize; +glm::vec3 PolyVoxEntityItem::getVoxelVolumeSize() const { + glm::vec3 voxelVolumeSize; + withReadLock([&] { + voxelVolumeSize = _voxelVolumeSize; + }); + return voxelVolumeSize; } @@ -226,12 +229,16 @@ void PolyVoxEntityItem::debugDump() const { } void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - QWriteLocker(&this->_voxelDataLock); - _voxelData = voxelData; - _voxelDataDirty = true; + withWriteLock([&] { + _voxelData = voxelData; + _voxelDataDirty = true; + }); } const QByteArray PolyVoxEntityItem::getVoxelData() const { - QReadLocker(&this->_voxelDataLock); - return _voxelData; + QByteArray voxelDataCopy; + withReadLock([&] { + voxelDataCopy = _voxelData; + }); + return voxelDataCopy; } diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 13e541d298..7441b34c9c 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -52,7 +52,7 @@ class PolyVoxEntityItem : public EntityItem { virtual void debugDump() const; virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); - virtual const glm::vec3& getVoxelVolumeSize() const; + virtual glm::vec3 getVoxelVolumeSize() const; virtual void setVoxelData(QByteArray voxelData); virtual const QByteArray getVoxelData() const; @@ -128,12 +128,14 @@ class PolyVoxEntityItem : public EntityItem { virtual void rebakeMesh() {}; + void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); } + virtual void getMesh() {}; // recompute mesh + protected: glm::vec3 _voxelVolumeSize; // this is always 3 bytes - mutable QReadWriteLock _voxelDataLock; QByteArray _voxelData; - bool _voxelDataDirty; + bool _voxelDataDirty; // _voxelData has changed, things that depend on it should be updated PolyVoxSurfaceStyle _voxelSurfaceStyle;