From f563b2aeba603553d97e41b8a4cf6e4c35e5bbf6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 19 Aug 2015 19:15:04 -0700 Subject: [PATCH 01/29] clean up debugging prints --- .../src/RenderablePolyVoxEntityItem.cpp | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 5d139a719a..44e7bae53a 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -530,10 +530,6 @@ void RenderablePolyVoxEntityItem::compressVolumeData() { QByteArray newVoxelData; QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); - #ifdef WANT_DEBUG - qDebug() << "compressing voxel data of size:" << voxelXSize << voxelYSize << voxelZSize; - #endif - writer << voxelXSize << voxelYSize << voxelZSize; QByteArray compressedData = qCompress(uncompressedData, 9); @@ -542,10 +538,6 @@ void RenderablePolyVoxEntityItem::compressVolumeData() { // make sure the compressed data can be sent over the wire-protocol if (newVoxelData.size() < 1150) { _voxelData = newVoxelData; - #ifdef WANT_DEBUG - qDebug() << "-------------- voxel compresss --------------"; - qDebug() << "raw-size =" << rawSize << " compressed-size =" << newVoxelData.size(); - #endif } else { // HACK -- until we have a way to allow for properties larger than MTU, don't update. #ifdef WANT_DEBUG @@ -559,13 +551,19 @@ void RenderablePolyVoxEntityItem::compressVolumeData() { _needsModelReload = true; #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::compressVolumeData" << (usecTimestampNow() - startTime) << getName(); + qDebug() << "RenderablePolyVoxEntityItem::compressVolumeData" << (usecTimestampNow() - startTime) << getName() + << voxelXSize << voxelYSize << voxelZSize + << "raw-size =" << rawSize << " compressed-size =" << newVoxelData.size(); #endif } // take compressed data and expand it into _volData. void RenderablePolyVoxEntityItem::decompressVolumeData() { + #ifdef WANT_DEBUG + auto startTime = usecTimestampNow(); + #endif + QDataStream reader(_voxelData); quint16 voxelXSize, voxelYSize, voxelZSize; reader >> voxelXSize; @@ -610,6 +608,10 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() { _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; _needsModelReload = true; getModel(); + + #ifdef WANT_DEBUG + qDebug() << "RenderablePolyVoxEntityItem::decompressVolumeData" << (usecTimestampNow() - startTime) << getName(); + #endif } // virtual @@ -634,11 +636,15 @@ bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo"; + auto startTime = usecTimestampNow(); #endif + ShapeType type = getShapeType(); if (type != SHAPE_TYPE_COMPOUND) { EntityItem::computeShapeInfo(info); + #ifdef WANT_DEBUG + qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo -- shape isn't compound."; + #endif return; } @@ -756,6 +762,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { if (_points.isEmpty()) { EntityItem::computeShapeInfo(info); + #ifdef WANT_DEBUG + qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo -- no points."; + #endif return; } @@ -763,6 +772,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { QByteArray b64 = _voxelData.toBase64(); info.setParams(type, collisionModelDimensions, QString(b64)); info.setConvexHulls(_points); + #ifdef WANT_DEBUG + qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo" << (usecTimestampNow() - startTime) << getName(); + #endif } void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { @@ -830,15 +842,12 @@ void RenderablePolyVoxEntityItem::getModel() { sizeof(PolyVox::PositionMaterialNormal), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); - #ifdef WANT_DEBUG - qDebug() << "---- vecIndices.size() =" << vecIndices.size(); - qDebug() << "---- vecVertices.size() =" << vecVertices.size(); - #endif - _needsModelReload = false; #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::getModel" << (usecTimestampNow() - startTime) << getName(); + qDebug() << "RenderablePolyVoxEntityItem::getModel" << (usecTimestampNow() - startTime) << getName() + << "vecIndices.size() =" << vecIndices.size() + << "vecVertices.size() =" << vecVertices.size(); #endif } From ef6116465d516de9d8f0dfa98a81bcdc98e96b1c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 19 Aug 2015 19:15:36 -0700 Subject: [PATCH 02/29] make edged-cubic be the default polyvox surface style --- examples/edit.js | 2 +- libraries/entities/src/PolyVoxEntityItem.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index deda035d5e..60666a54a5 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -452,7 +452,7 @@ var toolBar = (function () { type: "PolyVox", dimensions: { x: 10, y: 10, z: 10 }, voxelVolumeSize: {x:16, y:16, z:16}, - voxelSurfaceStyle: 1 + voxelSurfaceStyle: 2 }); return true; diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 30dded3714..bb9a63cf4c 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -25,7 +25,7 @@ const glm::vec3 PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE = glm::vec3(32, 32, const float PolyVoxEntityItem::MAX_VOXEL_DIMENSION = 128.0f; const QByteArray PolyVoxEntityItem::DEFAULT_VOXEL_DATA(PolyVoxEntityItem::makeEmptyVoxelData()); const PolyVoxEntityItem::PolyVoxSurfaceStyle PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE = - PolyVoxEntityItem::SURFACE_MARCHING_CUBES; + PolyVoxEntityItem::SURFACE_EDGED_CUBIC; const QString PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL = QString(""); const QString PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL = QString(""); const QString PolyVoxEntityItem::DEFAULT_Z_TEXTURE_URL = QString(""); From 6db0442fda75b899566619b4dfe467301021948d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 21 Aug 2015 10:48:26 -0700 Subject: [PATCH 03/29] allow for asynchronous building of collision shape --- interface/src/Application.cpp | 3 ++- interface/src/avatar/AvatarMotionState.cpp | 9 +++++-- interface/src/avatar/AvatarMotionState.h | 4 ++- libraries/physics/src/EntityMotionState.cpp | 27 +++++++++++++++---- libraries/physics/src/EntityMotionState.h | 8 +++--- libraries/physics/src/ObjectMotionState.cpp | 13 ++++++--- libraries/physics/src/ObjectMotionState.h | 8 +++--- .../physics/src/PhysicalEntitySimulation.cpp | 6 +++++ .../physics/src/PhysicalEntitySimulation.h | 1 + libraries/physics/src/PhysicsEngine.cpp | 18 +++++++++---- libraries/physics/src/PhysicsEngine.h | 2 +- 11 files changed, 75 insertions(+), 24 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5f8b17f178..0b794deb2b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2804,7 +2804,8 @@ void Application::update(float deltaTime) { _entities.getTree()->lockForWrite(); _entitySimulation.lock(); - _physicsEngine.changeObjects(_entitySimulation.getObjectsToChange()); + VectorOfMotionStates stillNeedChange = _physicsEngine.changeObjects(_entitySimulation.getObjectsToChange()); + _entitySimulation.setObjectsToChange(stillNeedChange); _entitySimulation.unlock(); _entities.getTree()->unlock(); diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index b106ee6398..cabe545f5a 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -28,15 +28,20 @@ AvatarMotionState::~AvatarMotionState() { } // virtual -uint32_t AvatarMotionState::getAndClearIncomingDirtyFlags() { +uint32_t AvatarMotionState::getIncomingDirtyFlags() { uint32_t dirtyFlags = 0; if (_body && _avatar) { dirtyFlags = _dirtyFlags; - _dirtyFlags = 0; } return dirtyFlags; } +void AvatarMotionState::clearIncomingDirtyFlags() { + if (_body && _avatar) { + _dirtyFlags = 0; + } +} + MotionType AvatarMotionState::computeObjectMotionType() const { // TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting) return MOTION_TYPE_DYNAMIC; diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index ac813e764c..1c49705f23 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -25,7 +25,8 @@ public: virtual MotionType getMotionType() const { return _motionType; } - virtual uint32_t getAndClearIncomingDirtyFlags(); + virtual uint32_t getIncomingDirtyFlags(); + virtual void clearIncomingDirtyFlags(); virtual MotionType computeObjectMotionType() const; @@ -65,6 +66,7 @@ public: friend class AvatarManager; protected: + virtual bool isReadyToComputeShape() { return true; } virtual btCollisionShape* computeNewShape(); virtual void clearObjectBackPointer(); Avatar* _avatar; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 4e6eb8353c..dc3c0ea4e8 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -99,7 +99,7 @@ void EntityMotionState::updateServerPhysicsVariables() { } // virtual -void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { assert(entityTreeIsLocked()); updateServerPhysicsVariables(); ObjectMotionState::handleEasyChanges(flags, engine); @@ -131,13 +131,15 @@ void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { _body->activate(); } + + return true; } // virtual -void EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { updateServerPhysicsVariables(); - ObjectMotionState::handleHardAndEasyChanges(flags, engine); + return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } void EntityMotionState::clearObjectBackPointer() { @@ -222,6 +224,15 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { #endif } + +// virtual and protected +bool EntityMotionState::isReadyToComputeShape() { + if (_entity) { + return _entity->isReadyToComputeShape(); + } + return false; +} + // virtual and protected btCollisionShape* EntityMotionState::computeNewShape() { if (_entity) { @@ -493,12 +504,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _lastStep = step; } -uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() { +uint32_t EntityMotionState::getIncomingDirtyFlags() { assert(entityTreeIsLocked()); uint32_t dirtyFlags = 0; if (_body && _entity) { dirtyFlags = _entity->getDirtyFlags(); - _entity->clearDirtyFlags(); // we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings int bodyFlags = _body->getCollisionFlags(); bool isMoving = _entity->isMoving(); @@ -510,6 +520,13 @@ uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() { return dirtyFlags; } +void EntityMotionState::clearIncomingDirtyFlags() { + assert(entityTreeIsLocked()); + if (_body && _entity) { + _entity->clearDirtyFlags(); + } +} + // virtual quint8 EntityMotionState::getSimulationPriority() const { if (_entity) { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 009c931dba..f3a2e80070 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -29,8 +29,8 @@ public: virtual ~EntityMotionState(); void updateServerPhysicsVariables(); - virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine); - virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem virtual MotionType computeObjectMotionType() const; @@ -48,7 +48,8 @@ public: bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID); void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step); - virtual uint32_t getAndClearIncomingDirtyFlags(); + virtual uint32_t getIncomingDirtyFlags(); + virtual void clearIncomingDirtyFlags(); void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; } void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; } @@ -91,6 +92,7 @@ protected: bool entityTreeIsLocked() const; #endif + virtual bool isReadyToComputeShape(); virtual btCollisionShape* computeNewShape(); virtual void clearObjectBackPointer(); virtual void setMotionType(MotionType motionType); diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index aeec85f7ea..85bddd0495 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -125,7 +125,7 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { } } -void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { if (flags & EntityItem::DIRTY_POSITION) { btTransform worldTrans; if (flags & EntityItem::DIRTY_ROTATION) { @@ -156,11 +156,16 @@ void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) if (flags & EntityItem::DIRTY_MASS) { updateBodyMassProperties(); } + + return true; } -void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { +bool ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { if (flags & EntityItem::DIRTY_SHAPE) { // make sure the new shape is valid + if (!isReadyToComputeShape()) { + return false; + } btCollisionShape* newShape = computeNewShape(); if (!newShape) { qCDebug(physics) << "Warning: failed to generate new shape!"; @@ -172,7 +177,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* if (flags & EASY_DIRTY_PHYSICS_FLAGS) { handleEasyChanges(flags, engine); } - return; + return true; } } getShapeManager()->releaseShape(_shape); @@ -192,6 +197,8 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* if (flags & HARD_DIRTY_PHYSICS_FLAGS) { engine->reinsertObject(this); } + + return true; } void ObjectMotionState::updateBodyMaterialProperties() { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 30394ef5fc..1bdf8b6372 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -71,8 +71,8 @@ public: ObjectMotionState(btCollisionShape* shape); ~ObjectMotionState(); - virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine); - virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine); + virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); void updateBodyMaterialProperties(); void updateBodyVelocities(); @@ -92,7 +92,8 @@ public: glm::vec3 getBodyAngularVelocity() const; virtual glm::vec3 getObjectLinearVelocityChange() const; - virtual uint32_t getAndClearIncomingDirtyFlags() = 0; + virtual uint32_t getIncomingDirtyFlags() = 0; + virtual void clearIncomingDirtyFlags() = 0; virtual MotionType computeObjectMotionType() const = 0; @@ -132,6 +133,7 @@ public: friend class PhysicsEngine; protected: + virtual bool isReadyToComputeShape() = 0; virtual btCollisionShape* computeNewShape() = 0; void setMotionType(MotionType motionType); diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index f6f02b8573..97cf6a549a 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -173,6 +173,12 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() { return _tempVector; } +void PhysicalEntitySimulation::setObjectsToChange(VectorOfMotionStates& objectsToChange) { + for (auto object : objectsToChange) { + _pendingChanges.insert(static_cast(object)); + } +} + VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() { _tempVector.clear(); for (auto stateItr : _pendingChanges) { diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 9c439c53b2..dc65128ac5 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -46,6 +46,7 @@ protected: // only called by EntitySimulation public: VectorOfMotionStates& getObjectsToDelete(); VectorOfMotionStates& getObjectsToAdd(); + void setObjectsToChange(VectorOfMotionStates& objectsToChange); VectorOfMotionStates& getObjectsToChange(); void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID); diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 022468633f..e8e6f68c6b 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -140,7 +140,7 @@ void PhysicsEngine::addObject(ObjectMotionState* motionState) { int16_t group = motionState->computeCollisionGroup(); _dynamicsWorld->addRigidBody(body, group, getCollisionMask(group)); - motionState->getAndClearIncomingDirtyFlags(); + motionState->clearIncomingDirtyFlags(); } void PhysicsEngine::removeObject(ObjectMotionState* object) { @@ -188,15 +188,23 @@ void PhysicsEngine::addObjects(VectorOfMotionStates& objects) { } } -void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) { +VectorOfMotionStates PhysicsEngine::changeObjects(VectorOfMotionStates& objects) { + VectorOfMotionStates stillNeedChange; for (auto object : objects) { - uint32_t flags = object->getAndClearIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; + uint32_t flags = object->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; if (flags & HARD_DIRTY_PHYSICS_FLAGS) { - object->handleHardAndEasyChanges(flags, this); + if (object->handleHardAndEasyChanges(flags, this)) { + object->clearIncomingDirtyFlags(); + stillNeedChange.push_back(object); + } } else if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - object->handleEasyChanges(flags, this); + if (object->handleEasyChanges(flags, this)) { + object->clearIncomingDirtyFlags(); + stillNeedChange.push_back(object); + } } } + return stillNeedChange; } void PhysicsEngine::reinsertObject(ObjectMotionState* object) { diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index a974a02e51..67b38323cc 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -60,7 +60,7 @@ public: void deleteObjects(VectorOfMotionStates& objects); void deleteObjects(SetOfMotionStates& objects); // only called during teardown void addObjects(VectorOfMotionStates& objects); - void changeObjects(VectorOfMotionStates& objects); + VectorOfMotionStates changeObjects(VectorOfMotionStates& objects); void reinsertObject(ObjectMotionState* object); void stepSimulation(); From 2c840ae0ccb8a1f82253a6c2fb4b3950db4f2767 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 21 Aug 2015 11:02:11 -0700 Subject: [PATCH 04/29] change default polyvox surface to edged-cubic and turn some voxels on when a polyvox is first rezzed --- examples/edit.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/edit.js b/examples/edit.js index 60666a54a5..f2260f14c2 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -448,12 +448,31 @@ var toolBar = (function () { } if (newPolyVoxButton === toolBar.clicked(clickedOverlay)) { - createNewEntity({ + var polyVoxId = createNewEntity({ type: "PolyVox", dimensions: { x: 10, y: 10, z: 10 }, voxelVolumeSize: {x:16, y:16, z:16}, voxelSurfaceStyle: 2 }); + for (var x = 1; x <= 14; x++) { + Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 1}, 255); + Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 1}, 255); + Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 14}, 255); + Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 14}, 255); + } + for (var y = 2; y <= 13; y++) { + Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 1}, 255); + Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 1}, 255); + Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 14}, 255); + Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 14}, 255); + } + for (var z = 2; z <= 13; z++) { + Entities.setVoxel(polyVoxId, {x: 1, y: 1, z: z}, 255); + Entities.setVoxel(polyVoxId, {x: 14, y: 1, z: z}, 255); + Entities.setVoxel(polyVoxId, {x: 1, y: 14, z: z}, 255); + Entities.setVoxel(polyVoxId, {x: 14, y: 14, z: z}, 255); + } + return true; } From 99374167299f987484142426d1b5d1963118461f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 21 Aug 2015 11:23:13 -0700 Subject: [PATCH 05/29] attempt to move some time-consuming polyvox stuff off of the main thread --- .../src/RenderablePolyVoxEntityItem.cpp | 271 +++++++++++++----- .../src/RenderablePolyVoxEntityItem.h | 29 +- 2 files changed, 221 insertions(+), 79 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 44e7bae53a..459f901a2d 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -9,8 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#define WANT_DEBUG 1 + #include #include +#include #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push @@ -64,7 +67,9 @@ RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& ent } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { + _volDataLock.lockForWrite(); delete _volData; + _volDataLock.unlock(); } bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, @@ -99,7 +104,9 @@ bool inBounds(const PolyVox::SimpleVolume* vol, int x, int y, int z) { void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { + _volDataLock.lockForWrite(); if (_volData && voxelVolumeSize == _voxelVolumeSize) { + _volDataLock.unlock(); return; } @@ -150,6 +157,8 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) } } + _volDataLock.unlock(); + // It's okay to decompress the old data here, because the data includes its original dimensions along // with the voxel data, and writing voxels outside the bounds of the new space is harmless. This allows // adjusting of the voxel-space size without overly mangling the shape. Shrinking the space and then @@ -163,16 +172,19 @@ void RenderablePolyVoxEntityItem::updateVoxelSurfaceStyle(PolyVoxSurfaceStyle vo bool willBeEdged = (voxelSurfaceStyle == SURFACE_EDGED_CUBIC || voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES); if (wasEdged != willBeEdged) { + _volDataLock.lockForWrite(); if (_volData) { delete _volData; } _volData = nullptr; _voxelSurfaceStyle = voxelSurfaceStyle; + _volDataLock.unlock(); setVoxelVolumeSize(_voxelVolumeSize); } else { _voxelSurfaceStyle = voxelSurfaceStyle; } - _needsModelReload = true; + bumpDataVersion(); + getModel(); } void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { @@ -224,8 +236,10 @@ glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { } uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { + _volDataLock.lockForRead(); assert(_volData); if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + _volDataLock.unlock(); return 0; } @@ -233,48 +247,55 @@ uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { // voxels all around the requested voxel space. Having the empty voxels around // the edges changes how the surface extractor behaves. + uint8_t result; if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - return _volData->getVoxelAt(x + 1, y + 1, z + 1); + result = _volData->getVoxelAt(x + 1, y + 1, z + 1); + } else { + result = _volData->getVoxelAt(x, y, z); } - return _volData->getVoxelAt(x, y, z); + + _volDataLock.unlock(); + return result; } bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { // set a voxel without recompressing the voxel data - assert(_volData); bool result = false; if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return false; + return result; } result = updateOnCount(x, y, z, toValue); + _volDataLock.lockForWrite(); + assert(_volData); if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); } else { _volData->setVoxelAt(x, y, z, toValue); } + _volDataLock.unlock(); return result; } -void RenderablePolyVoxEntityItem::clearEdges() { - // if we are in an edged mode, make sure the outside surfaces are zeroed out. - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - for (int z = 0; z < _volData->getDepth(); z++) { - for (int y = 0; y < _volData->getHeight(); y++) { - for (int x = 0; x < _volData->getWidth(); x++) { - if (x == 0 || y == 0 || z == 0 || - x == _volData->getWidth() - 1 || - y == _volData->getHeight() - 1 || - z == _volData->getDepth() - 1) { - _volData->setVoxelAt(x, y, z, 0); - } - } - } - } - } -} +// void RenderablePolyVoxEntityItem::clearEdges() { +// // if we are in an edged mode, make sure the outside surfaces are zeroed out. +// if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { +// for (int z = 0; z < _volData->getDepth(); z++) { +// for (int y = 0; y < _volData->getHeight(); y++) { +// for (int x = 0; x < _volData->getWidth(); x++) { +// if (x == 0 || y == 0 || z == 0 || +// x == _volData->getWidth() - 1 || +// y == _volData->getHeight() - 1 || +// z == _volData->getDepth() - 1) { +// _volData->setVoxelAt(x, y, z, 0); +// } +// } +// } +// } +// } +// } bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { if (_locked) { @@ -413,7 +434,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o { // TODO -- correctly pick against marching-cube generated meshes - if (_needsModelReload || !precisionPicking) { + if (getDataVersion() != _modelVersion || !precisionPicking) { // just intersect with bounding box return true; } @@ -435,8 +456,11 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o PolyVox::Vector3DFloat endPoint(farInVoxel.x, farInVoxel.y, farInVoxel.z); PolyVox::RaycastResult raycastResult; + + _volDataLock.lockForRead(); RaycastFunctor callback(_volData); raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); + _volDataLock.unlock(); if (raycastResult == PolyVox::RaycastResults::Completed) { // the ray completed its path -- nothing was hit. @@ -548,7 +572,8 @@ void RenderablePolyVoxEntityItem::compressVolumeData() { } _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; - _needsModelReload = true; + bumpDataVersion(); + getModel(); #ifdef WANT_DEBUG qDebug() << "RenderablePolyVoxEntityItem::compressVolumeData" << (usecTimestampNow() - startTime) << getName() @@ -598,7 +623,7 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() { } } } - clearEdges(); + // clearEdges(); #ifdef WANT_DEBUG qDebug() << "--------------- voxel decompress ---------------"; @@ -606,7 +631,7 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() { #endif _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; - _needsModelReload = true; + bumpDataVersion(); getModel(); #ifdef WANT_DEBUG @@ -622,41 +647,70 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const { return SHAPE_TYPE_NONE; } - bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { - if (_needsModelReload) { + #ifdef WANT_DEBUG + qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape" << getName() + << ", _shapeInfoWorkerRunning is" << _shapeInfoWorkerRunning + << ", _shapeInfoReady is" << _shapeInfoReady; + #endif + + getModel(); + + if (getDataVersion() != _modelVersion) { + qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape getModel running"; return false; } - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape" << (!_needsModelReload); - #endif - return true; + if (_shapeInfoWorkerRunning) { + if (_shapeInfoReady) { + bool result = _shapeInfoWorker.result(); + _shapeInfoWorkerRunning = false; + qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape returning" << result; + return result; + } else { + qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape _shapeInfo isn't ready"; + return false; + } + } + + _shapeInfoReady = false; + _shapeInfoWorkerRunning = true; + _shapeInfoWorker = QtConcurrent::run(this, &RenderablePolyVoxEntityItem::computeShapeInfoWorker); + qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape spawning thread."; + return false; } -void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { - #ifdef WANT_DEBUG - auto startTime = usecTimestampNow(); - #endif +bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { + // #ifdef WANT_DEBUG + // auto startTime = usecTimestampNow(); + // #endif + + // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfoWorker starting" << getName(); ShapeType type = getShapeType(); if (type != SHAPE_TYPE_COMPOUND) { - EntityItem::computeShapeInfo(info); - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo -- shape isn't compound."; - #endif - return; + EntityItem::computeShapeInfo(_shapeInfo); + // #ifdef WANT_DEBUG + // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo -- shape isn't compound." << getName(); + // #endif + _shapeInfoReady = true; + // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfoWorker ending, type != SHAPE_TYPE_COMPOUND" << getName(); + return true; } - _points.clear(); + 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; - /* pull top-facing triangles into polyhedrons so they can be walked on */ - const model::MeshPointer& mesh = _modelGeometry.getMesh(); + + _modelGeometryLock.lockForRead(); + const model::MeshPointer mesh = _modelGeometry.getMesh(); + _modelGeometryLock.unlock(); + const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); gpu::BufferView::Iterator it = indexBufferView.cbegin(); @@ -690,9 +744,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { pointsInPart << p3Model; // add next convex hull QVector newMeshPoints; - _points << newMeshPoints; + points << newMeshPoints; // add points to the new convex hull - _points[i++] << pointsInPart; + points[i++] << pointsInPart; } } else { unsigned int i = 0; @@ -751,30 +805,48 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { // add next convex hull QVector newMeshPoints; - _points << newMeshPoints; + points << newMeshPoints; // add points to the new convex hull - _points[i++] << pointsInPart; + points[i++] << pointsInPart; } } } } } - if (_points.isEmpty()) { - EntityItem::computeShapeInfo(info); - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo -- no points."; - #endif - return; + if (points.isEmpty()) { + EntityItem::computeShapeInfo(_shapeInfo); + // #ifdef WANT_DEBUG + // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo -- no points."; + // #endif + _shapeInfoReady = true; + // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfoWorker ending, no points" << getName(); + return true; } glm::vec3 collisionModelDimensions = box.getDimensions(); QByteArray b64 = _voxelData.toBase64(); - info.setParams(type, collisionModelDimensions, QString(b64)); - info.setConvexHulls(_points); - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo" << (usecTimestampNow() - startTime) << getName(); - #endif + _shapeInfo.setParams(type, collisionModelDimensions, QString(b64)); + _shapeInfo.setConvexHulls(points); + + // #ifdef WANT_DEBUG + // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo" << (usecTimestampNow() - startTime) << getName(); + // #endif + + _shapeInfoReady = true; + // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfoWorker ending, success" << getName(); + return true; +} + +void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { + qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo _shapeInfoReady is" << _shapeInfoReady; + + if (_shapeInfoReady) { + info = _shapeInfo; + } else { + qWarning() << "RenderablePolyVoxEntityItem::computeShapeInfo called when _shapeInfoReady was false"; + assert(false); + } } void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { @@ -789,14 +861,31 @@ void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) { PolyVoxEntityItem::setZTextureURL(zTextureURL); } -void RenderablePolyVoxEntityItem::getModel() { - #ifdef WANT_DEBUG - auto startTime = usecTimestampNow(); - #endif +quint64 RenderablePolyVoxEntityItem::getDataVersion() const { + _dataVersionLock.lockForRead(); + auto result = _dataVersion; + _dataVersionLock.unlock(); + return result; +} + +void RenderablePolyVoxEntityItem::bumpDataVersion() { + qDebug() << "data version going from" << _dataVersion << "to" << (_dataVersion + 1); + _dataVersionLock.lockForWrite(); + _dataVersion ++; + _dataVersionLock.unlock(); +} + +bool RenderablePolyVoxEntityItem::getModelWorker() { + model::Mesh* mesh = new model::Mesh(); + model::MeshPointer meshPtr(mesh); // A mesh object to hold the result of surface extraction PolyVox::SurfaceMesh polyVoxMesh; + _volDataLock.lockForRead(); + _dataVersionLock.lockForRead(); + _modelWorkerVersion = _dataVersion; + _dataVersionLock.unlock(); switch (_voxelSurfaceStyle) { case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { @@ -813,10 +902,9 @@ void RenderablePolyVoxEntityItem::getModel() { break; } } + _volDataLock.unlock(); // convert PolyVox mesh to a Sam mesh - auto mesh = _modelGeometry.getMesh(); - const std::vector& vecIndices = polyVoxMesh.getIndices(); auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), (gpu::Byte*)vecIndices.data()); @@ -824,7 +912,6 @@ void RenderablePolyVoxEntityItem::getModel() { 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()); @@ -842,13 +929,41 @@ void RenderablePolyVoxEntityItem::getModel() { sizeof(PolyVox::PositionMaterialNormal), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); - _needsModelReload = false; + _modelGeometryLock.lockForWrite(); + _modelGeometry.setMesh(meshPtr); + _modelGeometryLock.unlock(); - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::getModel" << (usecTimestampNow() - startTime) << getName() - << "vecIndices.size() =" << vecIndices.size() - << "vecVertices.size() =" << vecVertices.size(); - #endif + qDebug() << "******* RenderablePolyVoxEntityItem::getModelWorker is done *********" << _modelVersion << _modelWorkerVersion; + + _getModelWorkerDone = true; + _modelVersion = _modelWorkerVersion; + return true; +} + +void RenderablePolyVoxEntityItem::getModel() { + // if (QThread::currentThread() != thread()) { + // QMetaObject::invokeMethod(this, "getModel"); + // return; + // } + + if (getDataVersion() == _modelVersion) { + return; + } + + qDebug() << "RenderablePolyVoxEntityItem::getModel _getModelWorkerRunning =" << _getModelWorkerRunning; + + if (_getModelWorkerRunning) { + if (_getModelWorkerDone) { + if (!_getModelWorker.result()) { + qWarning() << "RenderablePolyVoxEntityItem::getModel worker failed"; + } + _getModelWorkerRunning = false; + } + return; + } + _getModelWorkerRunning = true; + _getModelWorkerDone = false; + _getModelWorker = QtConcurrent::run(this, &RenderablePolyVoxEntityItem::getModelWorker); } void RenderablePolyVoxEntityItem::render(RenderArgs* args) { @@ -856,6 +971,8 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::PolyVox); Q_ASSERT(args->_batch); + getModel(); + if (!_pipeline) { gpu::ShaderPointer vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(polyvox_vert))); gpu::ShaderPointer pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(polyvox_frag))); @@ -876,14 +993,18 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } - if (_needsModelReload) { - getModel(); - } +// if (_needsModelReload) { +// getModel(); +// // return; +// } gpu::Batch& batch = *args->_batch; batch.setPipeline(_pipeline); + _modelGeometryLock.lockForRead(); auto mesh = _modelGeometry.getMesh(); + _modelGeometryLock.unlock(); + Transform transform(voxelToWorldMatrix()); batch.setModelTransform(transform); batch.setInputFormat(mesh->getVertexFormat()); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index e2fcdedc31..40375ce337 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -12,6 +12,8 @@ #ifndef hifi_RenderablePolyVoxEntityItem_h #define hifi_RenderablePolyVoxEntityItem_h +#include + #include #include @@ -36,7 +38,6 @@ namespace render { template <> void payloadRender(const PolyVoxPayload::Pointer& payload, RenderArgs* args); } - class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -64,7 +65,10 @@ public: bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking) const; + quint64 getDataVersion() const; + void bumpDataVersion(); void getModel(); + bool getModelWorker(); virtual void setVoxelData(QByteArray voxelData); @@ -78,6 +82,7 @@ public: virtual ShapeType getShapeType() const; virtual bool isReadyToComputeShape(); virtual void computeShapeInfo(ShapeInfo& info); + virtual bool computeShapeInfoWorker(); virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const; virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3& worldCoords) const; @@ -116,10 +121,21 @@ private: void clearEdges(); PolyVox::SimpleVolume* _volData = nullptr; - model::Geometry _modelGeometry; - bool _needsModelReload = true; + mutable QReadWriteLock _volDataLock; // lock for _volData and _dataVersion - QVector> _points; // XXX + model::Geometry _modelGeometry; + mutable QReadWriteLock _modelGeometryLock; + + // set data version higher than model version so that getModelWorker must complete once before they are equal. + quint64 _dataVersion = 1; + mutable QReadWriteLock _dataVersionLock; + + quint64 _modelVersion = 0; + bool _getModelWorkerRunning = false; + bool _getModelWorkerDone = false; + quint64 _modelWorkerVersion = 0; + + QFuture _getModelWorker; NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; @@ -130,6 +146,11 @@ private: const int MATERIAL_GPU_SLOT = 3; render::ItemID _myItem; static gpu::PipelinePointer _pipeline; + + ShapeInfo _shapeInfo; + bool _shapeInfoReady = false; + bool _shapeInfoWorkerRunning = false; + QFuture _shapeInfoWorker; }; From dc31c7e233cf48ccbaee7baa23f52513fdea703e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 23 Aug 2015 08:14:05 -0700 Subject: [PATCH 06/29] back out previous attempt at theading polyvox code. lay groundwork for threading that can be reasoned about --- .../src/RenderablePolyVoxEntityItem.cpp | 1009 ++++++++--------- .../src/RenderablePolyVoxEntityItem.h | 86 +- .../entities/src/EntityScriptingInterface.cpp | 13 - libraries/entities/src/PolyVoxEntityItem.cpp | 9 +- libraries/entities/src/PolyVoxEntityItem.h | 6 +- .../physics/src/PhysicalEntitySimulation.h | 2 + 6 files changed, 525 insertions(+), 600 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 459f901a2d..1b7d53549f 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -9,9 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#define WANT_DEBUG 1 - #include +#include #include #include @@ -36,7 +35,6 @@ #include #include #include -#include #include #include "model/Geometry.h" @@ -45,6 +43,8 @@ #include "polyvox_vert.h" #include "polyvox_frag.h" #include "RenderablePolyVoxEntityItem.h" +#include "EntityEditPacketSender.h" +#include "PhysicalEntitySimulation.h" gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; @@ -58,143 +58,84 @@ RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& ent PolyVoxEntityItem(entityItemID, properties), _xTexture(nullptr), _yTexture(nullptr), - _zTexture(nullptr) { + _zTexture(nullptr), + _async(DEFAULT_VOXEL_SURFACE_STYLE, DEFAULT_VOXEL_VOLUME_SIZE, this) { + model::Mesh* mesh = new model::Mesh(); model::MeshPointer meshPtr(mesh); - _modelGeometry.setMesh(meshPtr); - - setVoxelVolumeSize(_voxelVolumeSize); + receiveNewMesh(meshPtr, _modelVersion); } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { - _volDataLock.lockForWrite(); - delete _volData; - _volDataLock.unlock(); -} - -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. - switch (surfaceStyle) { - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: - case PolyVoxEntityItem::SURFACE_CUBIC: - if (x < 0 || y < 0 || z < 0 || - x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()) { - return false; - } - return true; - - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - if (x < 0 || y < 0 || z < 0 || - x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) { - return false; - } - return true; - } - - return false; } -bool inBounds(const PolyVox::SimpleVolume* vol, int x, int y, int z) { - // x, y, z are in polyvox volume coords - return !(x < 0 || y < 0 || z < 0 || x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()); +void RenderablePolyVoxEntityItem::receiveNewVoxelData(QByteArray newVoxelData, uint64_t dataVersion) { + _voxelData = newVoxelData; + _dataVersion = dataVersion; + + auto now = usecTimestampNow(); + setLastEdited(now); + setLastBroadcast(now); + + EntityItemProperties properties = getProperties(); + properties.setVoxelDataDirty(); + properties.setLastEdited(now); + + EntityTreeElement* element = getElement(); + EntityTree* 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); + } } -void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - _volDataLock.lockForWrite(); - if (_volData && voxelVolumeSize == _voxelVolumeSize) { - _volDataLock.unlock(); - return; - } +void RenderablePolyVoxEntityItem::receiveNewMesh(model::MeshPointer newMeshPtr, uint64_t meshVersion) { + _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; + _modelGeometryLock.lockForWrite(); + _modelGeometry.setMesh(newMeshPtr); + _meshVersion = meshVersion; + _modelGeometryLock.unlock(); - #ifdef WANT_DEBUG - qDebug() << "resetting voxel-space size" << voxelVolumeSize.x << voxelVolumeSize.y << voxelVolumeSize.z; - #endif - - PolyVoxEntityItem::setVoxelVolumeSize(voxelVolumeSize); - - if (_volData) { - delete _volData; - } - - _onCount = 0; - - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - // 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, // -1 + 2 because these corners are inclusive - _voxelVolumeSize.y + 1, - _voxelVolumeSize.z + 1); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } else { - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x - 1, // -1 because these corners are inclusive - _voxelVolumeSize.y - 1, - _voxelVolumeSize.z - 1); - _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); - - #ifdef WANT_DEBUG - qDebug() << " new voxel-space size is" << _volData->getWidth() << _volData->getHeight() << _volData->getDepth(); - #endif - - // I'm not sure this is needed... the docs say that each element is initialized with its default - // constructor. I'll leave it here for now. - for (int z = 0; z < _volData->getDepth(); z++) { - for (int y = 0; y < _volData->getHeight(); y++) { - for (int x = 0; x < _volData->getWidth(); x++) { - _volData->setVoxelAt(x, y, z, 0); - } - } - } - - _volDataLock.unlock(); - - // It's okay to decompress the old data here, because the data includes its original dimensions along - // with the voxel data, and writing voxels outside the bounds of the new space is harmless. This allows - // adjusting of the voxel-space size without overly mangling the shape. Shrinking the space and then - // restoring the previous size (without any edits in between) will put the original shape back. - decompressVolumeData(); -} - -void RenderablePolyVoxEntityItem::updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - // if we are switching to or from "edged" we need to force a resize of _volData. - bool wasEdged = (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES); - bool willBeEdged = (voxelSurfaceStyle == SURFACE_EDGED_CUBIC || voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES); - - if (wasEdged != willBeEdged) { - _volDataLock.lockForWrite(); - if (_volData) { - delete _volData; - } - _volData = nullptr; - _voxelSurfaceStyle = voxelSurfaceStyle; - _volDataLock.unlock(); - setVoxelVolumeSize(_voxelVolumeSize); - } else { - _voxelSurfaceStyle = voxelSurfaceStyle; - } - bumpDataVersion(); - getModel(); + computeShapeInfoWorker(); } void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { if (voxelData == _voxelData) { return; } - PolyVoxEntityItem::setVoxelData(voxelData); - decompressVolumeData(); + + _voxelData = voxelData; + _async.decompressVolumeData(_voxelData, ++_modelVersion); } +void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { + if (voxelVolumeSize == _voxelVolumeSize) { + return; + } + + PolyVoxEntityItem::setVoxelVolumeSize(voxelVolumeSize); + _async.setVoxelVolumeSize(_voxelVolumeSize); + // decompress the old data here, because the data includes its original dimensions along with the voxel data, + // and writing voxels outside the bounds of the new space is harmless. This allows adjusting of the + // voxel-space size without overly mangling the shape. Shrinking the space and then restoring the previous + // size (without any edits in between) will put the original shape back. + _async.decompressVolumeData(_voxelData, ++_modelVersion); +} + +void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { + if (_voxelSurfaceStyle == voxelSurfaceStyle) { + return; + } + + _voxelSurfaceStyle = voxelSurfaceStyle; + _async.updateVoxelSurfaceStyle(_voxelSurfaceStyle, ++_modelVersion); +} + + glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units switch (_voxelSurfaceStyle) { @@ -236,118 +177,21 @@ glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { } uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { - _volDataLock.lockForRead(); - assert(_volData); - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - _volDataLock.unlock(); - return 0; - } - - // if _voxelSurfaceStyle is SURFACE_EDGED_CUBIC, we maintain an extra layer of - // voxels all around the requested voxel space. Having the empty voxels around - // the edges changes how the surface extractor behaves. - - uint8_t result; - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - result = _volData->getVoxelAt(x + 1, y + 1, z + 1); - } else { - result = _volData->getVoxelAt(x, y, z); - } - - _volDataLock.unlock(); - return result; + return _async.getVoxel(x, y, z); } -bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { - // set a voxel without recompressing the voxel data - bool result = false; - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return result; - } - - result = updateOnCount(x, y, z, toValue); - - _volDataLock.lockForWrite(); - assert(_volData); - if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { - _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); - } else { - _volData->setVoxelAt(x, y, z, toValue); - } - - _volDataLock.unlock(); - return result; -} - -// void RenderablePolyVoxEntityItem::clearEdges() { -// // if we are in an edged mode, make sure the outside surfaces are zeroed out. -// if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) { -// for (int z = 0; z < _volData->getDepth(); z++) { -// for (int y = 0; y < _volData->getHeight(); y++) { -// for (int x = 0; x < _volData->getWidth(); x++) { -// if (x == 0 || y == 0 || z == 0 || -// x == _volData->getWidth() - 1 || -// y == _volData->getHeight() - 1 || -// z == _volData->getDepth() - 1) { -// _volData->setVoxelAt(x, y, z, 0); -// } -// } -// } -// } -// } -// } - bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { if (_locked) { return false; } - bool result = setVoxelInternal(x, y, z, toValue); - if (result) { - compressVolumeData(); - } - return result; -} - -bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toValue) { - // keep _onCount up to date - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return false; - } - - uint8_t uVoxelValue = getVoxel(x, y, z); - if (toValue != 0) { - if (uVoxelValue == 0) { - _onCount++; - return true; - } - } else { - // toValue == 0 - if (uVoxelValue != 0) { - _onCount--; - assert(_onCount >= 0); - return true; - } - } - return false; + return _async.setVoxel(x, y, z, toValue, ++_modelVersion); } bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { - bool result = false; if (_locked) { return false; } - - 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); - } - } - } - if (result) { - compressVolumeData(); - } - return result; + return _async.setAll(toValue, ++_modelVersion); } bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t toValue) { @@ -360,30 +204,10 @@ bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t t } bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { - bool result = false; if (_locked) { return false; } - - // This three-level for loop iterates over every voxel in the volume - 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 make it solid. - if (fDistToCenter <= radius) { - result |= setVoxelInternal(x, y, z, toValue); - } - } - } - } - if (result) { - compressVolumeData(); - } - return result; + return _async.setSphereInVolume(center, radius, toValue, ++_modelVersion); } bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float radiusWorldCoords, uint8_t toValue) { @@ -402,6 +226,13 @@ public: _result(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)), _vol(vol) { } + + static bool inBounds(const PolyVox::SimpleVolume* vol, int x, int y, int z) { + // x, y, z are in polyvox volume coords + return !(x < 0 || y < 0 || z < 0 || x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()); + } + + bool operator()(PolyVox::SimpleVolume::Sampler& sampler) { PolyVox::Vector3DInt32 positionIndex = sampler.getPosition(); @@ -433,8 +264,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o bool precisionPicking) const { // TODO -- correctly pick against marching-cube generated meshes - - if (getDataVersion() != _modelVersion || !precisionPicking) { + if (!precisionPicking) { // just intersect with bounding box return true; } @@ -452,24 +282,13 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f); - PolyVox::Vector3DFloat startPoint(originInVoxel.x, originInVoxel.y, originInVoxel.z); - PolyVox::Vector3DFloat endPoint(farInVoxel.x, farInVoxel.y, farInVoxel.z); - - PolyVox::RaycastResult raycastResult; - - _volDataLock.lockForRead(); - RaycastFunctor callback(_volData); - raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); - _volDataLock.unlock(); - + glm::vec4 result; + PolyVox::RaycastResult raycastResult = _async.doRayCast(originInVoxel, farInVoxel, result); if (raycastResult == PolyVox::RaycastResults::Completed) { // the ray completed its path -- nothing was hit. return false; } - // result is in voxel-space coordinates. - glm::vec4 result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); - // set up ray tests against each face of the voxel. glm::vec3 minXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.0f, 0.5f, 0.5f, 0.0f))); glm::vec3 maxXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(1.0f, 0.5f, 0.5f, 0.0f))); @@ -523,125 +342,9 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o return true; } - -// 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::compressVolumeData() { - #ifdef WANT_DEBUG - auto startTime = usecTimestampNow(); - #endif - - quint16 voxelXSize = _voxelVolumeSize.x; - quint16 voxelYSize = _voxelVolumeSize.y; - quint16 voxelZSize = _voxelVolumeSize.z; - int rawSize = voxelXSize * voxelYSize * voxelZSize; - - QByteArray uncompressedData = QByteArray(rawSize, '\0'); - - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - uint8_t uVoxelValue = getVoxel(x, y, z); - 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) { - _voxelData = newVoxelData; - } else { - // HACK -- until we have a way to allow for properties larger than MTU, don't update. - #ifdef WANT_DEBUG - qDebug() << "voxel data too large, reverting change."; - #endif - // revert the active voxel-space to the last version that fit. - decompressVolumeData(); - } - - _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; - bumpDataVersion(); - getModel(); - - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::compressVolumeData" << (usecTimestampNow() - startTime) << getName() - << voxelXSize << voxelYSize << voxelZSize - << "raw-size =" << rawSize << " compressed-size =" << newVoxelData.size(); - #endif -} - - -// take compressed data and expand it into _volData. -void RenderablePolyVoxEntityItem::decompressVolumeData() { - #ifdef WANT_DEBUG - auto startTime = usecTimestampNow(); - #endif - - QDataStream reader(_voxelData); - quint16 voxelXSize, voxelYSize, voxelZSize; - reader >> voxelXSize; - reader >> voxelYSize; - reader >> voxelZSize; - - if (voxelXSize == 0 || voxelXSize > MAX_VOXEL_DIMENSION || - voxelYSize == 0 || voxelYSize > MAX_VOXEL_DIMENSION || - voxelZSize == 0 || voxelZSize > MAX_VOXEL_DIMENSION) { - qDebug() << "voxelSize is not reasonable, skipping decompressions." - << voxelXSize << voxelYSize << voxelZSize; - 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(); - return; - } - - 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]); - } - } - } - // clearEdges(); - - #ifdef WANT_DEBUG - qDebug() << "--------------- voxel decompress ---------------"; - qDebug() << "raw-size =" << rawSize << " compressed-size =" << _voxelData.size(); - #endif - - _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; - bumpDataVersion(); - getModel(); - - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::decompressVolumeData" << (usecTimestampNow() - startTime) << getName(); - #endif -} - // virtual ShapeType RenderablePolyVoxEntityItem::getShapeType() const { - if (_onCount > 0) { + if (_async.getOnCount() > 0) { return SHAPE_TYPE_COMPOUND; } return SHAPE_TYPE_NONE; @@ -649,52 +352,16 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const { bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape" << getName() - << ", _shapeInfoWorkerRunning is" << _shapeInfoWorkerRunning - << ", _shapeInfoReady is" << _shapeInfoReady; + qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape" << getName() << _shapeVersion << _modelVersion; #endif - getModel(); - - if (getDataVersion() != _modelVersion) { - qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape getModel running"; - return false; - } - - if (_shapeInfoWorkerRunning) { - if (_shapeInfoReady) { - bool result = _shapeInfoWorker.result(); - _shapeInfoWorkerRunning = false; - qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape returning" << result; - return result; - } else { - qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape _shapeInfo isn't ready"; - return false; - } - } - - _shapeInfoReady = false; - _shapeInfoWorkerRunning = true; - _shapeInfoWorker = QtConcurrent::run(this, &RenderablePolyVoxEntityItem::computeShapeInfoWorker); - qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape spawning thread."; - return false; + return (_shapeVersion == _modelVersion); } bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { - // #ifdef WANT_DEBUG - // auto startTime = usecTimestampNow(); - // #endif - - // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfoWorker starting" << getName(); - ShapeType type = getShapeType(); if (type != SHAPE_TYPE_COMPOUND) { EntityItem::computeShapeInfo(_shapeInfo); - // #ifdef WANT_DEBUG - // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo -- shape isn't compound." << getName(); - // #endif - _shapeInfoReady = true; - // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfoWorker ending, type != SHAPE_TYPE_COMPOUND" << getName(); return true; } @@ -816,11 +483,6 @@ bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { if (points.isEmpty()) { EntityItem::computeShapeInfo(_shapeInfo); - // #ifdef WANT_DEBUG - // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo -- no points."; - // #endif - _shapeInfoReady = true; - // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfoWorker ending, no points" << getName(); return true; } @@ -829,24 +491,12 @@ bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { _shapeInfo.setParams(type, collisionModelDimensions, QString(b64)); _shapeInfo.setConvexHulls(points); - // #ifdef WANT_DEBUG - // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo" << (usecTimestampNow() - startTime) << getName(); - // #endif - - _shapeInfoReady = true; - // qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfoWorker ending, success" << getName(); + _shapeVersion = _modelVersion; return true; } void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { - qDebug() << "RenderablePolyVoxEntityItem::computeShapeInfo _shapeInfoReady is" << _shapeInfoReady; - - if (_shapeInfoReady) { - info = _shapeInfo; - } else { - qWarning() << "RenderablePolyVoxEntityItem::computeShapeInfo called when _shapeInfoReady was false"; - assert(false); - } + info = _shapeInfo; } void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { @@ -861,118 +511,11 @@ void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) { PolyVoxEntityItem::setZTextureURL(zTextureURL); } -quint64 RenderablePolyVoxEntityItem::getDataVersion() const { - _dataVersionLock.lockForRead(); - auto result = _dataVersion; - _dataVersionLock.unlock(); - return result; -} - -void RenderablePolyVoxEntityItem::bumpDataVersion() { - qDebug() << "data version going from" << _dataVersion << "to" << (_dataVersion + 1); - _dataVersionLock.lockForWrite(); - _dataVersion ++; - _dataVersionLock.unlock(); -} - -bool RenderablePolyVoxEntityItem::getModelWorker() { - model::Mesh* mesh = new model::Mesh(); - model::MeshPointer meshPtr(mesh); - - // A mesh object to hold the result of surface extraction - PolyVox::SurfaceMesh polyVoxMesh; - - _volDataLock.lockForRead(); - _dataVersionLock.lockForRead(); - _modelWorkerVersion = _dataVersion; - _dataVersionLock.unlock(); - switch (_voxelSurfaceStyle) { - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - case PolyVoxEntityItem::SURFACE_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - } - _volDataLock.unlock(); - - // 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); - auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, - 0, - vertexBufferPtr->getSize() - sizeof(float) * 3, - 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))); - - _modelGeometryLock.lockForWrite(); - _modelGeometry.setMesh(meshPtr); - _modelGeometryLock.unlock(); - - qDebug() << "******* RenderablePolyVoxEntityItem::getModelWorker is done *********" << _modelVersion << _modelWorkerVersion; - - _getModelWorkerDone = true; - _modelVersion = _modelWorkerVersion; - return true; -} - -void RenderablePolyVoxEntityItem::getModel() { - // if (QThread::currentThread() != thread()) { - // QMetaObject::invokeMethod(this, "getModel"); - // return; - // } - - if (getDataVersion() == _modelVersion) { - return; - } - - qDebug() << "RenderablePolyVoxEntityItem::getModel _getModelWorkerRunning =" << _getModelWorkerRunning; - - if (_getModelWorkerRunning) { - if (_getModelWorkerDone) { - if (!_getModelWorker.result()) { - qWarning() << "RenderablePolyVoxEntityItem::getModel worker failed"; - } - _getModelWorkerRunning = false; - } - return; - } - _getModelWorkerRunning = true; - _getModelWorkerDone = false; - _getModelWorker = QtConcurrent::run(this, &RenderablePolyVoxEntityItem::getModelWorker); -} - void RenderablePolyVoxEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); assert(getType() == EntityTypes::PolyVox); Q_ASSERT(args->_batch); - getModel(); - if (!_pipeline) { gpu::ShaderPointer vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(polyvox_vert))); gpu::ShaderPointer pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(polyvox_frag))); @@ -993,11 +536,6 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } -// if (_needsModelReload) { -// getModel(); -// // return; -// } - gpu::Batch& batch = *args->_batch; batch.setPipeline(_pipeline); @@ -1104,3 +642,390 @@ glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxel glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& localCoords) const { return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 0.0f)); } + + +RenderablePolyVoxAsynchronous::RenderablePolyVoxAsynchronous(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, + glm::vec3 voxelVolumeSize, + RenderablePolyVoxEntityItem* owner) : + _voxelSurfaceStyle(voxelSurfaceStyle), + _owner(owner) { + setVoxelVolumeSize(voxelVolumeSize); +} + + +RenderablePolyVoxAsynchronous::~RenderablePolyVoxAsynchronous() { + delete _volData; +} + + +void RenderablePolyVoxAsynchronous::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { + #ifdef WANT_DEBUG + qDebug() << "resetting voxel-space size" << voxelVolumeSize.x << voxelVolumeSize.y << voxelVolumeSize.z; + #endif + + _voxelVolumeSize = voxelVolumeSize; + + if (_volData) { + delete _volData; + } + _onCount = 0; + + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + // 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); + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x - 1, // -1 because these corners are inclusive + _voxelVolumeSize.y - 1, + _voxelVolumeSize.z - 1); + _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); + + #ifdef WANT_DEBUG + qDebug() << " new voxel-space size is" << _volData->getWidth() << _volData->getHeight() << _volData->getDepth(); + #endif +} + + +void RenderablePolyVoxAsynchronous::updateVoxelSurfaceStyle(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, + uint64_t modelVersion) { + // if we are switching to or from "edged" we need to force a resize of _volData. + bool wasEdged = (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); + bool willBeEdged = (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); + + if (wasEdged != willBeEdged) { + if (_volData) { + delete _volData; + } + _volData = nullptr; + _voxelSurfaceStyle = voxelSurfaceStyle; + setVoxelVolumeSize(_voxelVolumeSize); + } else { + _voxelSurfaceStyle = voxelSurfaceStyle; + } + getModel(modelVersion); +} + + + +bool RenderablePolyVoxAsynchronous::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. + switch (surfaceStyle) { + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_CUBIC: + if (x < 0 || y < 0 || z < 0 || + x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()) { + return false; + } + return true; + + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + if (x < 0 || y < 0 || z < 0 || + x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) { + return false; + } + return true; + } + + return false; +} + + +uint8_t RenderablePolyVoxAsynchronous::getVoxel(int x, int y, int z) { + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return 0; + } + + // if _voxelSurfaceStyle is SURFACE_EDGED_CUBIC, we maintain an extra layer of + // voxels all around the requested voxel space. Having the empty voxels around + // the edges changes how the surface extractor behaves. + + uint8_t result; + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + result = _volData->getVoxelAt(x + 1, y + 1, z + 1); + } else { + result = _volData->getVoxelAt(x, y, z); + } + + return result; +} + + +bool RenderablePolyVoxAsynchronous::setVoxelInternal(int x, int y, int z, uint8_t toValue) { + // set a voxel without recompressing the voxel data + bool result = false; + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return result; + } + + result = updateOnCount(x, y, z, toValue); + + assert(_volData); + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); + } else { + _volData->setVoxelAt(x, y, z, toValue); + } + + return result; +} + + +bool RenderablePolyVoxAsynchronous::updateOnCount(int x, int y, int z, uint8_t toValue) { + // keep _onCount up to date + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return false; + } + + uint8_t uVoxelValue = getVoxel(x, y, z); + if (toValue != 0) { + if (uVoxelValue == 0) { + _onCount++; + return true; + } + } else { + // toValue == 0 + if (uVoxelValue != 0) { + _onCount--; + assert(_onCount >= 0); + return true; + } + } + return false; +} + + +bool RenderablePolyVoxAsynchronous::setVoxel(int x, int y, int z, uint8_t toValue, uint64_t modelVersion) { + bool result = setVoxelInternal(x, y, z, toValue); + if (result) { + compressVolumeData(modelVersion); + getModel(modelVersion); + } else { + // nothing changed. + _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + } + return result; +} + + +bool RenderablePolyVoxAsynchronous::setAll(uint8_t toValue, uint64_t modelVersion) { + bool result = false; + + 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); + } + } + } + if (result) { + compressVolumeData(modelVersion); + } else { + // nothing changed. + _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + } + return result; +} + + +bool RenderablePolyVoxAsynchronous::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue, uint64_t modelVersion) { + bool result = false; + + // This three-level for loop iterates over every voxel in the volume + 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 make it solid. + if (fDistToCenter <= radius) { + result |= setVoxelInternal(x, y, z, toValue); + } + } + } + } + if (result) { + compressVolumeData(modelVersion); + } else { + // nothing changed. + _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + } + + return result; +} + + +PolyVox::RaycastResult RenderablePolyVoxAsynchronous::doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, + glm::vec4& result) const { + PolyVox::Vector3DFloat startPoint(originInVoxel.x, originInVoxel.y, originInVoxel.z); + PolyVox::Vector3DFloat endPoint(farInVoxel.x, farInVoxel.y, farInVoxel.z); + + RaycastFunctor callback(_volData); + PolyVox::RaycastResult raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); + + // result is in voxel-space coordinates. + result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); + return raycastResult; +} + + +// take compressed data and expand it into _volData. +void RenderablePolyVoxAsynchronous::decompressVolumeData(QByteArray voxelData, uint64_t modelVersion) { + 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; + 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(); + return; + } + + 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]); + } + } + } + + getModel(modelVersion); +} + + +void RenderablePolyVoxAsynchronous::getModel(uint64_t modelVersion) { + model::Mesh* mesh = new model::Mesh(); + model::MeshPointer meshPtr(mesh); + + // A mesh object to hold the result of surface extraction + PolyVox::SurfaceMesh polyVoxMesh; + + switch (_voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + 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); + auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, + 0, + vertexBufferPtr->getSize() - sizeof(float) * 3, + 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))); + + _owner->receiveNewMesh(meshPtr, modelVersion); +} + +int RenderablePolyVoxAsynchronous::getOnCount() const { + // XXX get rid of this method + return _onCount; +} + + +// 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 RenderablePolyVoxAsynchronous::compressVolumeData(uint64_t modelVersion) { + quint16 voxelXSize = _voxelVolumeSize.x; + quint16 voxelYSize = _voxelVolumeSize.y; + quint16 voxelZSize = _voxelVolumeSize.z; + int rawSize = voxelXSize * voxelYSize * voxelZSize; + + QByteArray uncompressedData = QByteArray(rawSize, '\0'); + + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + uint8_t uVoxelValue = getVoxel(x, y, z); + 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) { + _owner->receiveNewVoxelData(newVoxelData, modelVersion); + } else { + // 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() << "voxel data too large, reverting change."; + decompressVolumeData(_owner->getVoxelData(), modelVersion); + } +} diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 40375ce337..4a767382d7 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -15,6 +15,7 @@ #include #include +#include #include #include "PolyVoxEntityItem.h" @@ -38,8 +39,47 @@ namespace render { template <> void payloadRender(const PolyVoxPayload::Pointer& payload, RenderArgs* args); } +class RenderablePolyVoxEntityItem; + + +class RenderablePolyVoxAsynchronous { + + public: + RenderablePolyVoxAsynchronous(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, glm::vec3 voxelVolumeSize, + RenderablePolyVoxEntityItem* owner); + ~RenderablePolyVoxAsynchronous(); + + void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); + void updateVoxelSurfaceStyle(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, uint64_t modelVersion); + uint8_t getVoxel(int x, int y, int z); + bool setVoxel(int x, int y, int z, uint8_t toValue, uint64_t modelVersion); + bool setAll(uint8_t toValue, uint64_t modelVersion); + bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue, uint64_t modelVersion); + PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; + int getOnCount() const; + void decompressVolumeData(QByteArray voxelData, uint64_t modelVersion); + +private: + void getModel(uint64_t modelVersion); + bool updateOnCount(int x, int y, int z, uint8_t new_value); + bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + void compressVolumeData(uint64_t modelVersion); + static bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z); + + PolyVoxEntityItem::PolyVoxSurfaceStyle _voxelSurfaceStyle; + glm::vec3 _voxelVolumeSize; + PolyVox::SimpleVolume* _volData = nullptr; + mutable QReadWriteLock _volDataLock; // lock for _volData + int _onCount = 0; // how many non-zero voxels are in _volData + + RenderablePolyVoxEntityItem* _owner = nullptr; +}; + + class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { -public: + + public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); RenderablePolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); @@ -57,22 +97,16 @@ public: virtual uint8_t getVoxel(int x, int y, int z); virtual bool setVoxel(int x, int y, int z, uint8_t toValue); - bool updateOnCount(int x, int y, int z, uint8_t new_value); - void render(RenderArgs* args); virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking) const; - quint64 getDataVersion() const; - void bumpDataVersion(); - void getModel(); - bool getModelWorker(); - virtual void setVoxelData(QByteArray voxelData); - virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); + virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle); + glm::vec3 getSurfacePositionAdjustment() const; glm::mat4 voxelToWorldMatrix() const; glm::mat4 worldToVoxelMatrix() const; @@ -108,49 +142,37 @@ public: std::shared_ptr scene, render::PendingChanges& pendingChanges); -protected: - virtual void updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle); + void receiveNewVoxelData(QByteArray newVoxelData, uint64_t dataVersion); + void receiveNewMesh(model::MeshPointer newMeshPtr, uint64_t meshVersion); private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. - bool setVoxelInternal(int x, int y, int z, uint8_t toValue); - void compressVolumeData(); - void decompressVolumeData(); - void clearEdges(); - - PolyVox::SimpleVolume* _volData = nullptr; - mutable QReadWriteLock _volDataLock; // lock for _volData and _dataVersion - model::Geometry _modelGeometry; mutable QReadWriteLock _modelGeometryLock; - // set data version higher than model version so that getModelWorker must complete once before they are equal. - quint64 _dataVersion = 1; - mutable QReadWriteLock _dataVersionLock; - - quint64 _modelVersion = 0; - bool _getModelWorkerRunning = false; - bool _getModelWorkerDone = false; - quint64 _modelWorkerVersion = 0; - QFuture _getModelWorker; NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; NetworkTexturePointer _zTexture; - int _onCount = 0; // how many non-zero voxels are in _volData - const int MATERIAL_GPU_SLOT = 3; render::ItemID _myItem; static gpu::PipelinePointer _pipeline; ShapeInfo _shapeInfo; - bool _shapeInfoReady = false; - bool _shapeInfoWorkerRunning = false; QFuture _shapeInfoWorker; + + // this does work outside of the main thread. + RenderablePolyVoxAsynchronous _async; + + uint64_t _modelVersion = 0; // local idea of how many changes have happened + // the following are compared against _modelVersion + uint64_t _meshVersion = 0; // version of most recently computed mesh + uint64_t _dataVersion = 0; // version of most recently compressed voxel data + uint64_t _shapeVersion = 0; // version of most recently computed collision shape }; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 41ca6a91ac..fee2055bd0 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -431,23 +431,10 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID, return false; } - auto now = usecTimestampNow(); - auto polyVoxEntity = std::dynamic_pointer_cast(entity); _entityTree->lockForWrite(); bool result = actor(*polyVoxEntity); - entity->setLastEdited(now); - entity->setLastBroadcast(now); _entityTree->unlock(); - - _entityTree->lockForRead(); - EntityItemProperties properties = entity->getProperties(); - _entityTree->unlock(); - - properties.setVoxelDataDirty(); - properties.setLastEdited(now); - - queueEntityMessage(PacketType::EntityEdit, entityID, properties); return result; } diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index bb9a63cf4c..1e42f26ff8 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -66,7 +66,7 @@ void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); - _voxelVolumeSize = voxelVolumeSize; + _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; @@ -184,10 +184,3 @@ void PolyVoxEntityItem::debugDump() const { qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } - -void PolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - if (voxelSurfaceStyle == _voxelSurfaceStyle) { - return; - } - updateVoxelSurfaceStyle(voxelSurfaceStyle); -} diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 9fbaade407..8e159900cd 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -62,7 +62,7 @@ class PolyVoxEntityItem : public EntityItem { SURFACE_EDGED_MARCHING_CUBES }; - void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle); + virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { _voxelSurfaceStyle = voxelSurfaceStyle; } // this other version of setVoxelSurfaceStyle is needed for SET_ENTITY_PROPERTY_FROM_PROPERTIES void setVoxelSurfaceStyle(uint16_t voxelSurfaceStyle) { setVoxelSurfaceStyle((PolyVoxSurfaceStyle) voxelSurfaceStyle); } virtual PolyVoxSurfaceStyle getVoxelSurfaceStyle() const { return _voxelSurfaceStyle; } @@ -104,10 +104,6 @@ class PolyVoxEntityItem : public EntityItem { virtual const QString& getZTextureURL() const { return _zTextureURL; } protected: - virtual void updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - _voxelSurfaceStyle = voxelSurfaceStyle; - } - glm::vec3 _voxelVolumeSize; // this is always 3 bytes QByteArray _voxelData; PolyVoxSurfaceStyle _voxelSurfaceStyle; diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index dc65128ac5..7599c7d1b5 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -52,6 +52,8 @@ public: void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID); void handleCollisionEvents(CollisionEvents& collisionEvents); + EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } + private: // incoming changes SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted) From 7cb99688646fe15c6bb780941611ce219f71d0ab Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 25 Aug 2015 05:53:36 -0700 Subject: [PATCH 07/29] more work on polyvox threading --- .../src/RenderablePolyVoxEntityItem.cpp | 282 ++++++++++++++---- .../src/RenderablePolyVoxEntityItem.h | 58 ++-- libraries/entities/src/PolyVoxEntityItem.cpp | 14 + libraries/entities/src/PolyVoxEntityItem.h | 5 +- 4 files changed, 281 insertions(+), 78 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 1b7d53549f..cc0a4f3e91 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -38,7 +38,6 @@ #include #include "model/Geometry.h" -#include "gpu/Context.h" #include "EntityTreeRenderer.h" #include "polyvox_vert.h" #include "polyvox_frag.h" @@ -46,6 +45,8 @@ #include "EntityEditPacketSender.h" #include "PhysicalEntitySimulation.h" +#define THREAD_POLYVOX 1 + gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; @@ -56,21 +57,46 @@ EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entit RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : PolyVoxEntityItem(entityItemID, properties), + _mesh(new model::Mesh()), _xTexture(nullptr), _yTexture(nullptr), _zTexture(nullptr), _async(DEFAULT_VOXEL_SURFACE_STYLE, DEFAULT_VOXEL_VOLUME_SIZE, this) { - model::Mesh* mesh = new model::Mesh(); - model::MeshPointer meshPtr(mesh); - receiveNewMesh(meshPtr, _modelVersion); + // model::Mesh* mesh = new model::Mesh(); + // model::MeshPointer meshPtr(mesh); + + // mesh->setIndexBuffer(nullptr); + + // auto vertexBuffer = std::make_shared(0, (gpu::Byte*)""); + // auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + // auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, + // 0, + // vertexBufferPtr->getSize() - sizeof(float) * 3, + // sizeof(PolyVox::PositionMaterialNormal), + // gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); + // mesh->setVertexBuffer(*vertexBufferView); + + // _mesh = meshPtr; + // _modelGeometry.setMesh(meshPtr); + + // qDebug() << "5d301155-faf9-44dd-8e8b-061a03d42c0f" << "IN CONSTRUCTOR" << getID() << _voxelData; + // _async.decompressVolumeData(_voxelData, ++_modelVersion); + // _async.updateVoxelSurfaceStyle(_voxelSurfaceStyle, ++_modelVersion); } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { } -void RenderablePolyVoxEntityItem::receiveNewVoxelData(QByteArray newVoxelData, uint64_t dataVersion) { +void RenderablePolyVoxEntityItem::receiveNewVoxelData(QByteArray newVoxelData, quint64 dataVersion) { + if (dataVersion <= _dataVersion) { + // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << dataVersion << _dataVersion; + return; + } + + // qDebug() << _id << "OKOKOK!!!" << QString(__PRETTY_FUNCTION__) << getName() << dataVersion << _dataVersion << "mesh-size is" << _mesh->getNumVertices(); + _voxelData = newVoxelData; _dataVersion = dataVersion; @@ -93,44 +119,79 @@ void RenderablePolyVoxEntityItem::receiveNewVoxelData(QByteArray newVoxelData, u } -void RenderablePolyVoxEntityItem::receiveNewMesh(model::MeshPointer newMeshPtr, uint64_t meshVersion) { +void RenderablePolyVoxEntityItem::receiveNewMesh(model::MeshPointer newMeshPtr, quint64 meshVersion) { + if (meshVersion <= _meshVersion) { + // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << meshVersion << _meshVersion; + return; + } + + // int meshSize = -1; + // const model::MeshPointer mesh = _modelGeometry.getMesh(); + // if (mesh) { + // mesh->getVertexBuffer()._size; + // } + + // qDebug() << _id << "OKOKOK!!!" << QString(__PRETTY_FUNCTION__) << getName() << meshVersion << _meshVersion << "mesh-size is" << meshSize; + // qDebug() << _id << "OKOKOK!!!" << QString(__PRETTY_FUNCTION__) << getName() << meshVersion << _meshVersion << "mesh-size is" << _mesh->getNumVertices() << "new mesh-size is" << newMeshPtr->getNumVertices(); + _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; _modelGeometryLock.lockForWrite(); - _modelGeometry.setMesh(newMeshPtr); + // _modelGeometry.setMesh(newMeshPtr); + _mesh = newMeshPtr; _meshVersion = meshVersion; _modelGeometryLock.unlock(); + // qDebug() << _id << "OKOKOK after" << getName() << " mesh-size is" << _modelGeometry.getMesh()->getVertexBuffer()._size << "\n"; + // qDebug() << _id << "OKOKOK after" << getName() << " mesh-size is" << _mesh->getNumVertices(); + // qDebug() << _id << "OKOKOK after" << getName() << " mesh-size is" << newMeshPtr->getNumVertices(); + computeShapeInfoWorker(); } void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - if (voxelData == _voxelData) { + // qDebug() << "5d301155-faf9-44dd-8e8b-061a03d42c0f setVoxelData for" << getName() << getID() << ((void *)this); + + _voxelDataLock.lockForWrite(); + if (/*_dataVersion > 1 &&*/ _voxelData == voxelData) { + // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << _dataVersion << getID() << ((void *)this); + _voxelDataLock.unlock(); return; } + // qDebug() << _id << "OKOKOK" << QString(__PRETTY_FUNCTION__) << getName() << _dataVersion; + _voxelData = voxelData; _async.decompressVolumeData(_voxelData, ++_modelVersion); + _voxelDataLock.unlock(); } void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - if (voxelVolumeSize == _voxelVolumeSize) { + if (/*_meshVersion > 1 && _shapeVersion > 1 &&*/ _voxelVolumeSize == voxelVolumeSize) { + // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << _meshVersion << ((void *)this); return; } + // qDebug() << _id << "OKOKOK" << QString(__PRETTY_FUNCTION__) << getName() << ((void *)this); + PolyVoxEntityItem::setVoxelVolumeSize(voxelVolumeSize); _async.setVoxelVolumeSize(_voxelVolumeSize); // decompress the old data here, because the data includes its original dimensions along with the voxel data, // and writing voxels outside the bounds of the new space is harmless. This allows adjusting of the // voxel-space size without overly mangling the shape. Shrinking the space and then restoring the previous // size (without any edits in between) will put the original shape back. + _voxelDataLock.lockForRead(); _async.decompressVolumeData(_voxelData, ++_modelVersion); + _voxelDataLock.unlock(); } void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - if (_voxelSurfaceStyle == voxelSurfaceStyle) { + if (/*_meshVersion > 1 && _shapeVersion > 1 &&*/ _voxelSurfaceStyle == voxelSurfaceStyle) { + // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << _meshVersion; return; } + // qDebug() << _id << "OKOKOK" << QString(__PRETTY_FUNCTION__) << getName(); + _voxelSurfaceStyle = voxelSurfaceStyle; _async.updateVoxelSurfaceStyle(_voxelSurfaceStyle, ++_modelVersion); } @@ -351,17 +412,15 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const { } bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { - #ifdef WANT_DEBUG - qDebug() << "RenderablePolyVoxEntityItem::isReadyToComputeShape" << getName() << _shapeVersion << _modelVersion; - #endif - return (_shapeVersion == _modelVersion); } bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { ShapeType type = getShapeType(); if (type != SHAPE_TYPE_COMPOUND) { + _shapeInfoLock.lockForWrite(); EntityItem::computeShapeInfo(_shapeInfo); + _shapeInfoLock.unlock(); return true; } @@ -374,12 +433,16 @@ bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { /* pull each triangle in the mesh into a polyhedron which can be collided with */ unsigned int i = 0; - _modelGeometryLock.lockForRead(); - const model::MeshPointer mesh = _modelGeometry.getMesh(); - _modelGeometryLock.unlock(); + // _modelGeometryLock.lockForRead(); + // const model::MeshPointer mesh = _modelGeometry.getMesh(); + // _modelGeometryLock.unlock(); + model::MeshPointer mesh = _mesh; + _modelGeometryLock.lockForRead(); const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); + _modelGeometryLock.unlock(); + gpu::BufferView::Iterator it = indexBufferView.cbegin(); while (it != indexBufferView.cend()) { uint32_t p0Index = *(it++); @@ -482,21 +545,26 @@ bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { } if (points.isEmpty()) { + _shapeInfoLock.lockForWrite(); EntityItem::computeShapeInfo(_shapeInfo); + _shapeInfoLock.unlock(); return true; } glm::vec3 collisionModelDimensions = box.getDimensions(); QByteArray b64 = _voxelData.toBase64(); + _shapeInfoLock.lockForWrite(); _shapeInfo.setParams(type, collisionModelDimensions, QString(b64)); _shapeInfo.setConvexHulls(points); - _shapeVersion = _modelVersion; + _shapeInfoLock.unlock(); return true; } void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { + _shapeInfoLock.lockForRead(); info = _shapeInfo; + _shapeInfoLock.unlock(); } void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { @@ -516,6 +584,18 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::PolyVox); Q_ASSERT(args->_batch); + if (_meshVersion < _modelVersion || + _dataVersion < _modelVersion) { + _async.decompressVolumeData(_voxelData, _modelVersion); + _async.updateVoxelSurfaceStyle(_voxelSurfaceStyle, _modelVersion); + } + + model::MeshPointer mesh = _mesh; + + // if (_meshVersion == 0) { + // return; // we have no mesh + // } + if (!_pipeline) { gpu::ShaderPointer vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(polyvox_vert))); gpu::ShaderPointer pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(polyvox_frag))); @@ -539,9 +619,8 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { gpu::Batch& batch = *args->_batch; batch.setPipeline(_pipeline); - _modelGeometryLock.lockForRead(); - auto mesh = _modelGeometry.getMesh(); - _modelGeometryLock.unlock(); + // _modelGeometryLock.lockForRead(); + // auto mesh = _modelGeometry.getMesh(); Transform transform(voxelToWorldMatrix()); batch.setModelTransform(transform); @@ -583,6 +662,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { batch._glUniform3f(voxelVolumeSizeLocation, _voxelVolumeSize.x, _voxelVolumeSize.y, _voxelVolumeSize.z); batch.drawIndexed(gpu::TRIANGLES, mesh->getNumIndices(), 0); + // _modelGeometryLock.unlock(); RenderableDebugableEntityItem::render(this, args); } @@ -648,21 +728,23 @@ RenderablePolyVoxAsynchronous::RenderablePolyVoxAsynchronous(PolyVoxEntityItem:: glm::vec3 voxelVolumeSize, RenderablePolyVoxEntityItem* owner) : _voxelSurfaceStyle(voxelSurfaceStyle), + _onCount(0), _owner(owner) { setVoxelVolumeSize(voxelVolumeSize); } RenderablePolyVoxAsynchronous::~RenderablePolyVoxAsynchronous() { + _volDataLock.lockForWrite(); delete _volData; + _volDataLock.unlock(); } void RenderablePolyVoxAsynchronous::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - #ifdef WANT_DEBUG - qDebug() << "resetting voxel-space size" << voxelVolumeSize.x << voxelVolumeSize.y << voxelVolumeSize.z; - #endif + // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName(); + _volDataLock.lockForWrite(); _voxelVolumeSize = voxelVolumeSize; if (_volData) { @@ -691,15 +773,16 @@ void RenderablePolyVoxAsynchronous::setVoxelVolumeSize(glm::vec3 voxelVolumeSize // having the "outside of voxel-space" value be 255 has helped me notice some problems. _volData->setBorderValue(255); - - #ifdef WANT_DEBUG - qDebug() << " new voxel-space size is" << _volData->getWidth() << _volData->getHeight() << _volData->getDepth(); - #endif + _volDataLock.unlock(); } void RenderablePolyVoxAsynchronous::updateVoxelSurfaceStyle(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, - uint64_t modelVersion) { + quint64 modelVersion) { + + // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName(); + + _volDataLock.lockForWrite(); // if we are switching to or from "edged" we need to force a resize of _volData. bool wasEdged = (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); @@ -712,11 +795,23 @@ void RenderablePolyVoxAsynchronous::updateVoxelSurfaceStyle(PolyVoxEntityItem::P } _volData = nullptr; _voxelSurfaceStyle = voxelSurfaceStyle; + _volDataLock.unlock(); setVoxelVolumeSize(_voxelVolumeSize); +# ifdef THREAD_POLYVOX + QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::decompressVolumeDataAsync, + _owner->getVoxelData(), modelVersion); +# else + decompressVolumeDataAsync(_owner->getVoxelData(), modelVersion); +# endif } else { _voxelSurfaceStyle = voxelSurfaceStyle; + _volDataLock.unlock(); +# ifdef THREAD_POLYVOX + QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); +# else + getMeshAsync(modelVersion); +# endif } - getModel(modelVersion); } @@ -748,6 +843,13 @@ bool RenderablePolyVoxAsynchronous::inUserBounds(const PolyVox::SimpleVolumereceiveNewVoxelData(_owner->getVoxelData(), modelVersion); + // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + _owner->setDataVersion(modelVersion); } return result; } -bool RenderablePolyVoxAsynchronous::setAll(uint8_t toValue, uint64_t modelVersion) { +bool RenderablePolyVoxAsynchronous::setAll(uint8_t toValue, quint64 modelVersion) { bool result = false; + _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++) { @@ -836,20 +947,29 @@ bool RenderablePolyVoxAsynchronous::setAll(uint8_t toValue, uint64_t modelVersio } } } + _volDataLock.unlock(); if (result) { +# ifdef THREAD_POLYVOX + QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::compressVolumeData, modelVersion); + QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); +# else compressVolumeData(modelVersion); + getMeshAsync(modelVersion); +# endif } else { // nothing changed. - _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + _owner->setDataVersion(modelVersion); } return result; } -bool RenderablePolyVoxAsynchronous::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue, uint64_t modelVersion) { +bool RenderablePolyVoxAsynchronous::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue, quint64 modelVersion) { bool result = false; // 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++) { @@ -864,11 +984,19 @@ bool RenderablePolyVoxAsynchronous::setSphereInVolume(glm::vec3 center, float ra } } } + _volDataLock.unlock(); if (result) { +# ifdef THREAD_POLYVOX + QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::compressVolumeData, modelVersion); + QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); +# else compressVolumeData(modelVersion); + getMeshAsync(modelVersion); +# endif } else { // nothing changed. - _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + _owner->setDataVersion(modelVersion); } return result; @@ -880,8 +1008,10 @@ PolyVox::RaycastResult RenderablePolyVoxAsynchronous::doRayCast(glm::vec4 origin 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(); // result is in voxel-space coordinates. result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); @@ -890,7 +1020,21 @@ PolyVox::RaycastResult RenderablePolyVoxAsynchronous::doRayCast(glm::vec4 origin // take compressed data and expand it into _volData. -void RenderablePolyVoxAsynchronous::decompressVolumeData(QByteArray voxelData, uint64_t modelVersion) { +void RenderablePolyVoxAsynchronous::decompressVolumeData(QByteArray voxelData, quint64 modelVersion) { + // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName() << modelVersion; + +# ifdef THREAD_POLYVOX + QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::decompressVolumeDataAsync, voxelData, modelVersion); +# else + decompressVolumeDataAsync(voxelData, modelVersion); +# endif +} + + +void RenderablePolyVoxAsynchronous::decompressVolumeDataAsync(QByteArray voxelData, quint64 modelVersion) { + // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) + // << _owner->getName() << _owner->getID() << modelVersion; + QDataStream reader(voxelData); quint16 voxelXSize, voxelYSize, voxelZSize; reader >> voxelXSize; @@ -901,7 +1045,10 @@ void RenderablePolyVoxAsynchronous::decompressVolumeData(QByteArray voxelData, u voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { qDebug() << "voxelSize is not reasonable, skipping decompressions." - << voxelXSize << voxelYSize << voxelZSize; + << voxelXSize << voxelYSize << voxelZSize << _owner->getName() << _owner->getID() << modelVersion + << "5d301155-faf9-44dd-8e8b-061a03d42c0f"; + // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + _owner->setDataVersion(modelVersion); return; } @@ -912,11 +1059,15 @@ void RenderablePolyVoxAsynchronous::decompressVolumeData(QByteArray voxelData, u 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(); + qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" + << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size() + << _owner->getName() << _owner->getID() << modelVersion << "5d301155-faf9-44dd-8e8b-061a03d42c0f"; + // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + _owner->setDataVersion(modelVersion); return; } + _volDataLock.lockForWrite(); for (int z = 0; z < voxelZSize; z++) { for (int y = 0; y < voxelYSize; y++) { for (int x = 0; x < voxelXSize; x++) { @@ -925,18 +1076,27 @@ void RenderablePolyVoxAsynchronous::decompressVolumeData(QByteArray voxelData, u } } } + _volDataLock.unlock(); - getModel(modelVersion); + _owner->receiveNewVoxelData(voxelData, modelVersion); + +# ifdef THREAD_POLYVOX + QFuture future = QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); +# else + getMeshAsync(modelVersion); +# endif } -void RenderablePolyVoxAsynchronous::getModel(uint64_t modelVersion) { - model::Mesh* mesh = new model::Mesh(); - model::MeshPointer meshPtr(mesh); +void RenderablePolyVoxAsynchronous::getMeshAsync(quint64 modelVersion) { + // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName() << modelVersion; + + model::MeshPointer mesh(new model::Mesh()); // A mesh object to hold the result of surface extraction PolyVox::SurfaceMesh polyVoxMesh; + _volDataLock.lockForRead(); switch (_voxelSurfaceStyle) { case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { @@ -953,6 +1113,7 @@ void RenderablePolyVoxAsynchronous::getModel(uint64_t modelVersion) { break; } } + _volDataLock.unlock(); // convert PolyVox mesh to a Sam mesh const std::vector& vecIndices = polyVoxMesh.getIndices(); @@ -966,10 +1127,11 @@ void RenderablePolyVoxAsynchronous::getModel(uint64_t modelVersion) { auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), (gpu::Byte*)vecVertices.data()); auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, - 0, - vertexBufferPtr->getSize() - sizeof(float) * 3, - sizeof(PolyVox::PositionMaterialNormal), + 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, @@ -979,7 +1141,10 @@ void RenderablePolyVoxAsynchronous::getModel(uint64_t modelVersion) { sizeof(PolyVox::PositionMaterialNormal), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); - _owner->receiveNewMesh(meshPtr, modelVersion); + // qDebug() << _owner->getID() << "OKOKOK -- MADE NEW MESH" << "index-count =" << vecIndices.size() + // << "vertex-count =" << vecVertices.size() << "mesh-vertg-size =" << mesh->getVertexBuffer()._size; + + _owner->receiveNewMesh(mesh, modelVersion); } int RenderablePolyVoxAsynchronous::getOnCount() const { @@ -990,7 +1155,9 @@ int RenderablePolyVoxAsynchronous::getOnCount() const { // 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 RenderablePolyVoxAsynchronous::compressVolumeData(uint64_t modelVersion) { +void RenderablePolyVoxAsynchronous::compressVolumeData(quint64 modelVersion) { + // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName() << modelVersion; + quint16 voxelXSize = _voxelVolumeSize.x; quint16 voxelYSize = _voxelVolumeSize.y; quint16 voxelZSize = _voxelVolumeSize.z; @@ -998,10 +1165,11 @@ void RenderablePolyVoxAsynchronous::compressVolumeData(uint64_t modelVersion) { 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 = getVoxel(x, y, z); + uint8_t uVoxelValue = getVoxelInternal(x, y, z); int uncompressedIndex = z * voxelYSize * voxelXSize + y * voxelXSize + @@ -1010,6 +1178,7 @@ void RenderablePolyVoxAsynchronous::compressVolumeData(uint64_t modelVersion) { } } } + _volDataLock.unlock(); QByteArray newVoxelData; QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); @@ -1025,7 +1194,10 @@ void RenderablePolyVoxAsynchronous::compressVolumeData(uint64_t modelVersion) { } else { // 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() << "voxel data too large, reverting change."; - decompressVolumeData(_owner->getVoxelData(), modelVersion); + // QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::decompressVolumeDataAsync, + // _owner->getVoxelData(), modelVersion); + // decompressVolumeDataAsync(_owner->getVoxelData(), modelVersion); + // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); + _owner->setDataVersion(modelVersion); } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 4a767382d7..405cdd07ff 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -13,15 +13,17 @@ #define hifi_RenderablePolyVoxEntityItem_h #include +#include #include #include + #include #include "PolyVoxEntityItem.h" #include "RenderableDebugableEntityItem.h" #include "RenderableEntityItem.h" - +#include "gpu/Context.h" class PolyVoxPayload { public: @@ -42,7 +44,8 @@ namespace render { class RenderablePolyVoxEntityItem; -class RenderablePolyVoxAsynchronous { +class RenderablePolyVoxAsynchronous : public QObject { + Q_OBJECT public: RenderablePolyVoxAsynchronous(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, glm::vec3 voxelVolumeSize, @@ -50,20 +53,29 @@ class RenderablePolyVoxAsynchronous { ~RenderablePolyVoxAsynchronous(); void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); - void updateVoxelSurfaceStyle(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, uint64_t modelVersion); + void updateVoxelSurfaceStyle(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, quint64 modelVersion); uint8_t getVoxel(int x, int y, int z); - bool setVoxel(int x, int y, int z, uint8_t toValue, uint64_t modelVersion); - bool setAll(uint8_t toValue, uint64_t modelVersion); - bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue, uint64_t modelVersion); + bool setVoxel(int x, int y, int z, uint8_t toValue, quint64 modelVersion); + bool setAll(uint8_t toValue, quint64 modelVersion); + bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue, quint64 modelVersion); PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; int getOnCount() const; - void decompressVolumeData(QByteArray voxelData, uint64_t modelVersion); + void decompressVolumeData(QByteArray voxelData, quint64 modelVersion); + +// signals: +// void doDecompressVolumeDataAsync(QByteArray voxelData, quint64 modelVersion); +// void doGetMeshAsync(quint64 modelVersion); + + private: - void getModel(uint64_t modelVersion); + void decompressVolumeDataAsync(QByteArray voxelData, quint64 modelVersion); + void getMeshAsync(quint64 modelVersion); + + uint8_t getVoxelInternal(int x, int y, int z); bool updateOnCount(int x, int y, int z, uint8_t new_value); bool setVoxelInternal(int x, int y, int z, uint8_t toValue); - void compressVolumeData(uint64_t modelVersion); + void compressVolumeData(quint64 modelVersion); static bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, int x, int y, int z); @@ -71,7 +83,7 @@ private: glm::vec3 _voxelVolumeSize; PolyVox::SimpleVolume* _volData = nullptr; mutable QReadWriteLock _volDataLock; // lock for _volData - int _onCount = 0; // how many non-zero voxels are in _volData + std::atomic_int _onCount; // how many non-zero voxels are in _volData RenderablePolyVoxEntityItem* _owner = nullptr; }; @@ -116,7 +128,6 @@ class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { virtual ShapeType getShapeType() const; virtual bool isReadyToComputeShape(); virtual void computeShapeInfo(ShapeInfo& info); - virtual bool computeShapeInfoWorker(); virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const; virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3& worldCoords) const; @@ -142,17 +153,21 @@ class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { std::shared_ptr scene, render::PendingChanges& pendingChanges); - void receiveNewVoxelData(QByteArray newVoxelData, uint64_t dataVersion); - void receiveNewMesh(model::MeshPointer newMeshPtr, uint64_t meshVersion); + void receiveNewVoxelData(QByteArray newVoxelData, quint64 dataVersion); + void receiveNewMesh(model::MeshPointer newMeshPtr, quint64 meshVersion); + void setDataVersion(quint64 dataVersion) { _dataVersion = dataVersion; } private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. - model::Geometry _modelGeometry; - mutable QReadWriteLock _modelGeometryLock; + virtual bool computeShapeInfoWorker(); - QFuture _getModelWorker; + // model::Geometry _modelGeometry; + mutable QReadWriteLock _modelGeometryLock; + model::MeshPointer _mesh; + + QFuture _getMeshWorker; NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; @@ -163,16 +178,17 @@ private: static gpu::PipelinePointer _pipeline; ShapeInfo _shapeInfo; - QFuture _shapeInfoWorker; + // QFuture _shapeInfoWorker; + mutable QReadWriteLock _shapeInfoLock; // this does work outside of the main thread. RenderablePolyVoxAsynchronous _async; - uint64_t _modelVersion = 0; // local idea of how many changes have happened + quint64 _modelVersion = 1; // local idea of how many changes have happened // the following are compared against _modelVersion - uint64_t _meshVersion = 0; // version of most recently computed mesh - uint64_t _dataVersion = 0; // version of most recently compressed voxel data - uint64_t _shapeVersion = 0; // version of most recently computed collision shape + quint64 _meshVersion = 0; // version of most recently computed mesh + quint64 _dataVersion = 0; // version of most recently compressed voxel data + quint64 _shapeVersion = 0; // version of most recently computed collision shape }; diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 1e42f26ff8..df3b6d1608 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -184,3 +184,17 @@ void PolyVoxEntityItem::debugDump() const { qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } + +void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) +{ + qDebug() << "5d301155-faf9-44dd-8e8b-061a03d42c0f parent setVoxelData for" << getName() << getID() << ((void *)this) + << typeid(*this).name(); + + _voxelDataLock.lockForWrite(); + _voxelData = voxelData; + _voxelDataLock.unlock(); +} + +const QByteArray PolyVoxEntityItem::getVoxelData() const { + return _voxelData; +} diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 8e159900cd..118f6cc99b 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -52,8 +52,8 @@ class PolyVoxEntityItem : public EntityItem { virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); virtual const glm::vec3& getVoxelVolumeSize() const { return _voxelVolumeSize; } - virtual void setVoxelData(QByteArray voxelData) { _voxelData = voxelData; } - virtual const QByteArray& getVoxelData() const { return _voxelData; } + virtual void setVoxelData(QByteArray voxelData); + virtual const QByteArray getVoxelData() const; enum PolyVoxSurfaceStyle { SURFACE_MARCHING_CUBES, @@ -105,6 +105,7 @@ class PolyVoxEntityItem : public EntityItem { protected: glm::vec3 _voxelVolumeSize; // this is always 3 bytes + mutable QReadWriteLock _voxelDataLock; QByteArray _voxelData; PolyVoxSurfaceStyle _voxelSurfaceStyle; From caafea6e3b546ccaccb719d12e439eb3beba002b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 25 Aug 2015 11:46:34 -0700 Subject: [PATCH 08/29] allow compound collision shapes with less than 2 sub-shapes --- libraries/physics/src/ShapeFactory.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index cfe7abd4db..f138587030 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -94,7 +94,6 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { if (numSubShapes == 1) { shape = createConvexHull(info.getPoints()[0]); } else { - assert(numSubShapes > 1); auto compound = new btCompoundShape(); btTransform trans; trans.setIdentity(); From f6c440756c4661d13bfca40c798d817f1a680f4f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 25 Aug 2015 11:46:51 -0700 Subject: [PATCH 09/29] do slow polyvox operations on a thread --- .../src/RenderablePolyVoxEntityItem.cpp | 1402 ++++++++--------- .../src/RenderablePolyVoxEntityItem.h | 92 +- libraries/entities/src/PolyVoxEntityItem.cpp | 7 +- libraries/entities/src/PolyVoxEntityItem.h | 3 + 4 files changed, 652 insertions(+), 852 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index cc0a4f3e91..c35a80f188 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -58,142 +58,57 @@ RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& ent const EntityItemProperties& properties) : PolyVoxEntityItem(entityItemID, properties), _mesh(new model::Mesh()), + _meshDirty(true), _xTexture(nullptr), _yTexture(nullptr), - _zTexture(nullptr), - _async(DEFAULT_VOXEL_SURFACE_STYLE, DEFAULT_VOXEL_VOLUME_SIZE, this) { - - // model::Mesh* mesh = new model::Mesh(); - // model::MeshPointer meshPtr(mesh); - - // mesh->setIndexBuffer(nullptr); - - // auto vertexBuffer = std::make_shared(0, (gpu::Byte*)""); - // auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - // auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, - // 0, - // vertexBufferPtr->getSize() - sizeof(float) * 3, - // sizeof(PolyVox::PositionMaterialNormal), - // gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); - // mesh->setVertexBuffer(*vertexBufferView); - - // _mesh = meshPtr; - // _modelGeometry.setMesh(meshPtr); - - // qDebug() << "5d301155-faf9-44dd-8e8b-061a03d42c0f" << "IN CONSTRUCTOR" << getID() << _voxelData; - // _async.decompressVolumeData(_voxelData, ++_modelVersion); - // _async.updateVoxelSurfaceStyle(_voxelSurfaceStyle, ++_modelVersion); + _zTexture(nullptr) { + setVoxelVolumeSize(_voxelVolumeSize); } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { } -void RenderablePolyVoxEntityItem::receiveNewVoxelData(QByteArray newVoxelData, quint64 dataVersion) { - if (dataVersion <= _dataVersion) { - // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << dataVersion << _dataVersion; - return; - } - - // qDebug() << _id << "OKOKOK!!!" << QString(__PRETTY_FUNCTION__) << getName() << dataVersion << _dataVersion << "mesh-size is" << _mesh->getNumVertices(); - - _voxelData = newVoxelData; - _dataVersion = dataVersion; - - auto now = usecTimestampNow(); - setLastEdited(now); - setLastBroadcast(now); - - EntityItemProperties properties = getProperties(); - properties.setVoxelDataDirty(); - properties.setLastEdited(now); - - EntityTreeElement* element = getElement(); - EntityTree* 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); - } -} - - -void RenderablePolyVoxEntityItem::receiveNewMesh(model::MeshPointer newMeshPtr, quint64 meshVersion) { - if (meshVersion <= _meshVersion) { - // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << meshVersion << _meshVersion; - return; - } - - // int meshSize = -1; - // const model::MeshPointer mesh = _modelGeometry.getMesh(); - // if (mesh) { - // mesh->getVertexBuffer()._size; - // } - - // qDebug() << _id << "OKOKOK!!!" << QString(__PRETTY_FUNCTION__) << getName() << meshVersion << _meshVersion << "mesh-size is" << meshSize; - // qDebug() << _id << "OKOKOK!!!" << QString(__PRETTY_FUNCTION__) << getName() << meshVersion << _meshVersion << "mesh-size is" << _mesh->getNumVertices() << "new mesh-size is" << newMeshPtr->getNumVertices(); - - _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; - _modelGeometryLock.lockForWrite(); - // _modelGeometry.setMesh(newMeshPtr); - _mesh = newMeshPtr; - _meshVersion = meshVersion; - _modelGeometryLock.unlock(); - - // qDebug() << _id << "OKOKOK after" << getName() << " mesh-size is" << _modelGeometry.getMesh()->getVertexBuffer()._size << "\n"; - // qDebug() << _id << "OKOKOK after" << getName() << " mesh-size is" << _mesh->getNumVertices(); - // qDebug() << _id << "OKOKOK after" << getName() << " mesh-size is" << newMeshPtr->getNumVertices(); - - computeShapeInfoWorker(); -} - void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - // qDebug() << "5d301155-faf9-44dd-8e8b-061a03d42c0f setVoxelData for" << getName() << getID() << ((void *)this); - _voxelDataLock.lockForWrite(); - if (/*_dataVersion > 1 &&*/ _voxelData == voxelData) { - // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << _dataVersion << getID() << ((void *)this); + if (_voxelData == voxelData) { _voxelDataLock.unlock(); return; } - // qDebug() << _id << "OKOKOK" << QString(__PRETTY_FUNCTION__) << getName() << _dataVersion; - _voxelData = voxelData; - _async.decompressVolumeData(_voxelData, ++_modelVersion); + _voxelDataDirty = true; _voxelDataLock.unlock(); + decompressVolumeData(); } -void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - if (/*_meshVersion > 1 && _shapeVersion > 1 &&*/ _voxelVolumeSize == voxelVolumeSize) { - // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << _meshVersion << ((void *)this); - return; - } - - // qDebug() << _id << "OKOKOK" << QString(__PRETTY_FUNCTION__) << getName() << ((void *)this); - - PolyVoxEntityItem::setVoxelVolumeSize(voxelVolumeSize); - _async.setVoxelVolumeSize(_voxelVolumeSize); - // decompress the old data here, because the data includes its original dimensions along with the voxel data, - // and writing voxels outside the bounds of the new space is harmless. This allows adjusting of the - // voxel-space size without overly mangling the shape. Shrinking the space and then restoring the previous - // size (without any edits in between) will put the original shape back. - _voxelDataLock.lockForRead(); - _async.decompressVolumeData(_voxelData, ++_modelVersion); - _voxelDataLock.unlock(); -} void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - if (/*_meshVersion > 1 && _shapeVersion > 1 &&*/ _voxelSurfaceStyle == voxelSurfaceStyle) { - // qDebug() << _id << "OKOKOK NOPE" << QString(__PRETTY_FUNCTION__) << getName() << _meshVersion; + if (_voxelSurfaceStyle == voxelSurfaceStyle) { return; } - // qDebug() << _id << "OKOKOK" << QString(__PRETTY_FUNCTION__) << getName(); + // if we are switching to or from "edged" we need to force a resize of _volData. + bool wasEdged = (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); + bool willBeEdged = (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); - _voxelSurfaceStyle = voxelSurfaceStyle; - _async.updateVoxelSurfaceStyle(_voxelSurfaceStyle, ++_modelVersion); + if (wasEdged != willBeEdged) { + _volDataLock.lockForWrite(); + _volDataDirty = true; + if (_volData) { + delete _volData; + } + _volData = nullptr; + _voxelSurfaceStyle = voxelSurfaceStyle; + _volDataLock.unlock(); + setVoxelVolumeSize(_voxelVolumeSize); + decompressVolumeData(); + } else { + _voxelSurfaceStyle = voxelSurfaceStyle; + getMesh(); + } } @@ -210,6 +125,7 @@ glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { return glm::vec3(0.0f, 0.0f, 0.0f); } + glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units glm::vec3 center = getCenterPosition(); @@ -237,24 +153,47 @@ glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { return worldToModelMatrix; } -uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { - return _async.getVoxel(x, y, z); -} bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { + auto now = usecTimestampNow(); if (_locked) { return false; } - return _async.setVoxel(x, y, z, toValue, ++_modelVersion); + + _volDataLock.lockForWrite(); + bool result = setVoxelInternal(x, y, z, toValue); + _volDataDirty = true; + _volDataLock.unlock(); + compressVolumeDataAndSendEditPacket(); + + auto timeSpent = usecTimestampNow() - now; + qDebug() << "RenderablePolyVoxEntityItem::setVoxel timeSpent =" << timeSpent; + return result; } + bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { + bool result = false; if (_locked) { - return false; + return result; } - return _async.setAll(toValue, ++_modelVersion); + + _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); + } + } + } + _volDataLock.unlock(); + compressVolumeDataAndSendEditPacket(); + return result; } + + bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t toValue) { if (_locked) { return false; @@ -265,10 +204,31 @@ bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t t } bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { + bool result = false; if (_locked) { - return false; + return result; } - return _async.setSphereInVolume(center, radius, toValue, ++_modelVersion); + + // This three-level for loop iterates over every voxel in the volume + _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++) { + // 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); + } + } + } + } + _volDataLock.unlock(); + compressVolumeDataAndSendEditPacket(); + return result; } bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float radiusWorldCoords, uint8_t toValue) { @@ -344,7 +304,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f); glm::vec4 result; - PolyVox::RaycastResult raycastResult = _async.doRayCast(originInVoxel, farInVoxel, result); + PolyVox::RaycastResult raycastResult = doRayCast(originInVoxel, farInVoxel, result); if (raycastResult == PolyVox::RaycastResults::Completed) { // the ray completed its path -- nothing was hit. return false; @@ -405,25 +365,539 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o // virtual ShapeType RenderablePolyVoxEntityItem::getShapeType() const { - if (_async.getOnCount() > 0) { - return SHAPE_TYPE_COMPOUND; - } - return SHAPE_TYPE_NONE; + return SHAPE_TYPE_COMPOUND; } bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { - return (_shapeVersion == _modelVersion); + _meshLock.lockForRead(); + if (_meshDirty) { + _meshLock.unlock(); + computeShapeInfoWorker(); + return false; + } + _meshLock.unlock(); + return true; } -bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { - ShapeType type = getShapeType(); - if (type != SHAPE_TYPE_COMPOUND) { - _shapeInfoLock.lockForWrite(); - EntityItem::computeShapeInfo(_shapeInfo); - _shapeInfoLock.unlock(); - return true; +void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { + _shapeInfoLock.lockForRead(); + info = _shapeInfo; + _shapeInfoLock.unlock(); +} + +void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { + PolyVoxEntityItem::setXTextureURL(xTextureURL); +} + +void RenderablePolyVoxEntityItem::setYTextureURL(QString yTextureURL) { + PolyVoxEntityItem::setYTextureURL(yTextureURL); +} + +void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) { + PolyVoxEntityItem::setZTextureURL(zTextureURL); +} + +void RenderablePolyVoxEntityItem::render(RenderArgs* args) { + PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); + assert(getType() == EntityTypes::PolyVox); + Q_ASSERT(args->_batch); + + _volDataLock.lockForRead(); + if (_volDataDirty) { + getMesh(); + } + _volDataLock.unlock(); + + _meshLock.lockForRead(); + model::MeshPointer mesh = _mesh; + _meshLock.unlock(); + + if (!_pipeline) { + gpu::ShaderPointer vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(polyvox_vert))); + gpu::ShaderPointer pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(polyvox_frag))); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("xMap"), 0)); + slotBindings.insert(gpu::Shader::Binding(std::string("yMap"), 1)); + slotBindings.insert(gpu::Shader::Binding(std::string("zMap"), 2)); + + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + gpu::Shader::makeProgram(*program, slotBindings); + + auto state = std::make_shared(); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } + gpu::Batch& batch = *args->_batch; + batch.setPipeline(_pipeline); + + Transform transform(voxelToWorldMatrix()); + batch.setModelTransform(transform); + batch.setInputFormat(mesh->getVertexFormat()); + batch.setInputBuffer(gpu::Stream::POSITION, mesh->getVertexBuffer()); + batch.setInputBuffer(gpu::Stream::NORMAL, + mesh->getVertexBuffer()._buffer, + sizeof(float) * 3, + mesh->getVertexBuffer()._stride); + batch.setIndexBuffer(gpu::UINT32, mesh->getIndexBuffer()._buffer, 0); + + if (!_xTextureURL.isEmpty() && !_xTexture) { + _xTexture = DependencyManager::get()->getTexture(_xTextureURL); + } + if (!_yTextureURL.isEmpty() && !_yTexture) { + _yTexture = DependencyManager::get()->getTexture(_yTextureURL); + } + if (!_zTextureURL.isEmpty() && !_zTexture) { + _zTexture = DependencyManager::get()->getTexture(_zTextureURL); + } + + if (_xTexture) { + batch.setResourceTexture(0, _xTexture->getGPUTexture()); + } else { + batch.setResourceTexture(0, DependencyManager::get()->getWhiteTexture()); + } + if (_yTexture) { + batch.setResourceTexture(1, _yTexture->getGPUTexture()); + } else { + batch.setResourceTexture(1, DependencyManager::get()->getWhiteTexture()); + } + if (_zTexture) { + batch.setResourceTexture(2, _zTexture->getGPUTexture()); + } else { + batch.setResourceTexture(2, DependencyManager::get()->getWhiteTexture()); + } + + int voxelVolumeSizeLocation = _pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize"); + batch._glUniform3f(voxelVolumeSizeLocation, _voxelVolumeSize.x, _voxelVolumeSize.y, _voxelVolumeSize.z); + + batch.drawIndexed(gpu::TRIANGLES, mesh->getNumIndices(), 0); + + RenderableDebugableEntityItem::render(this, args); +} + +bool RenderablePolyVoxEntityItem::addToScene(EntityItemPointer self, + std::shared_ptr scene, + render::PendingChanges& pendingChanges) { + _myItem = scene->allocateID(); + + auto renderItem = std::make_shared(shared_from_this()); + auto renderData = PolyVoxPayload::Pointer(renderItem); + auto renderPayload = std::make_shared(renderData); + + pendingChanges.resetItem(_myItem, renderPayload); + + return true; +} + +void RenderablePolyVoxEntityItem::removeFromScene(EntityItemPointer self, + std::shared_ptr scene, + render::PendingChanges& pendingChanges) { + pendingChanges.removeItem(_myItem); +} + +namespace render { + template <> const ItemKey payloadGetKey(const PolyVoxPayload::Pointer& payload) { + return ItemKey::Builder::opaqueShape(); + } + + template <> const Item::Bound payloadGetBound(const PolyVoxPayload::Pointer& payload) { + if (payload && payload->_owner) { + auto polyVoxEntity = std::dynamic_pointer_cast(payload->_owner); + return polyVoxEntity->getAABox(); + } + return render::Item::Bound(); + } + + template <> void payloadRender(const PolyVoxPayload::Pointer& payload, RenderArgs* args) { + if (args && payload && payload->_owner) { + payload->_owner->render(args); + } + } +} + + +glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const { + return glm::vec3(voxelToWorldMatrix() * glm::vec4(voxelCoords, 1.0f)); +} + +glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3& worldCoords) const { + return glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); +} + +glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const { + return glm::vec3(voxelToLocalMatrix() * glm::vec4(voxelCoords, 0.0f)); +} + +glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& localCoords) const { + return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 0.0f)); +} + + +void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { + if (_volData && _voxelVolumeSize == voxelVolumeSize) { + return; + } + + _volDataLock.lockForWrite(); + _volDataDirty = true; + _voxelVolumeSize = voxelVolumeSize; + + if (_volData) { + delete _volData; + } + _onCount = 0; + + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + // 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); + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x - 1, // -1 because these corners are inclusive + _voxelVolumeSize.y - 1, + _voxelVolumeSize.z - 1); + _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(); +} + + +bool RenderablePolyVoxEntityItem::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. + switch (surfaceStyle) { + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_CUBIC: + if (x < 0 || y < 0 || z < 0 || + x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()) { + return false; + } + return true; + + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + if (x < 0 || y < 0 || z < 0 || + x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) { + return false; + } + return true; + } + + return false; +} + + +uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) { + _volDataLock.lockForRead(); + auto result = getVoxelInternal(x, y, z); + _volDataLock.unlock(); + return result; +} + + +uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) { + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return 0; + } + + // if _voxelSurfaceStyle is SURFACE_EDGED_CUBIC, we maintain an extra layer of + // voxels all around the requested voxel space. Having the empty voxels around + // the edges changes how the surface extractor behaves. + + uint8_t result; + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + result = _volData->getVoxelAt(x + 1, y + 1, z + 1); + } else { + result = _volData->getVoxelAt(x, y, z); + } + + return result; +} + + +bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { + // set a voxel without recompressing the voxel data + bool result = false; + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return result; + } + + result = updateOnCount(x, y, z, toValue); + + assert(_volData); + if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || + _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); + } else { + _volData->setVoxelAt(x, y, z, toValue); + } + + return result; +} + + +bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toValue) { + // keep _onCount up to date + if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { + return false; + } + + uint8_t uVoxelValue = getVoxelInternal(x, y, z); + if (toValue != 0) { + if (uVoxelValue == 0) { + _onCount++; + return true; + } + } else { + // toValue == 0 + if (uVoxelValue != 0) { + _onCount--; + assert(_onCount >= 0); + return true; + } + } + return false; +} + + +PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel, + glm::vec4 farInVoxel, + glm::vec4& result) const { + 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(); + + // result is in voxel-space coordinates. + result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); + return raycastResult; +} + + + +void RenderablePolyVoxEntityItem::decompressVolumeData() { + _threadRunning.acquire(); + QtConcurrent::run(this, &RenderablePolyVoxEntityItem::decompressVolumeDataAsync); +} + + +// 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(); + _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]); + } + } + } + _volDataLock.unlock(); + _threadRunning.release(); +} + +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 +void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync() { + quint16 voxelXSize = _voxelVolumeSize.x; + quint16 voxelYSize = _voxelVolumeSize.y; + quint16 voxelZSize = _voxelVolumeSize.z; + int rawSize = voxelXSize * voxelYSize * voxelZSize; + + 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); + + EntityTreeElement* element = getElement(); + EntityTree* 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(); +} + +void RenderablePolyVoxEntityItem::getMesh() { + _threadRunning.acquire(); + QtConcurrent::run(this, &RenderablePolyVoxEntityItem::getMeshAsync); +} + + +void RenderablePolyVoxEntityItem::getMeshAsync() { + model::MeshPointer mesh(new model::Mesh()); + + // A mesh object to hold the result of surface extraction + PolyVox::SurfaceMesh polyVoxMesh; + + _volDataLock.lockForRead(); + switch (_voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + 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))); + + _meshLock.lockForWrite(); + _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; + _mesh = mesh; + _meshDirty = true; + _meshLock.unlock(); + _volDataDirty = false; + _volDataLock.unlock(); + + _threadRunning.release(); +} + +void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { + _threadRunning.acquire(); + QtConcurrent::run(this, &RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync); +} + + +void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() { QVector> points; AABox box; glm::mat4 vtoM = voxelToLocalMatrix(); @@ -433,15 +907,11 @@ bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { /* pull each triangle in the mesh into a polyhedron which can be collided with */ unsigned int i = 0; - // _modelGeometryLock.lockForRead(); - // const model::MeshPointer mesh = _modelGeometry.getMesh(); - // _modelGeometryLock.unlock(); - + _meshLock.lockForRead(); model::MeshPointer mesh = _mesh; - _modelGeometryLock.lockForRead(); const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); - _modelGeometryLock.unlock(); + _meshLock.unlock(); gpu::BufferView::Iterator it = indexBufferView.cbegin(); while (it != indexBufferView.cend()) { @@ -548,656 +1018,20 @@ bool RenderablePolyVoxEntityItem::computeShapeInfoWorker() { _shapeInfoLock.lockForWrite(); EntityItem::computeShapeInfo(_shapeInfo); _shapeInfoLock.unlock(); - return true; + _threadRunning.release(); + return; } glm::vec3 collisionModelDimensions = box.getDimensions(); QByteArray b64 = _voxelData.toBase64(); _shapeInfoLock.lockForWrite(); - _shapeInfo.setParams(type, collisionModelDimensions, QString(b64)); + _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, QString(b64)); _shapeInfo.setConvexHulls(points); - _shapeVersion = _modelVersion; _shapeInfoLock.unlock(); - return true; -} - -void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { - _shapeInfoLock.lockForRead(); - info = _shapeInfo; - _shapeInfoLock.unlock(); -} - -void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { - PolyVoxEntityItem::setXTextureURL(xTextureURL); -} - -void RenderablePolyVoxEntityItem::setYTextureURL(QString yTextureURL) { - PolyVoxEntityItem::setYTextureURL(yTextureURL); -} - -void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) { - PolyVoxEntityItem::setZTextureURL(zTextureURL); -} - -void RenderablePolyVoxEntityItem::render(RenderArgs* args) { - PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); - assert(getType() == EntityTypes::PolyVox); - Q_ASSERT(args->_batch); - - if (_meshVersion < _modelVersion || - _dataVersion < _modelVersion) { - _async.decompressVolumeData(_voxelData, _modelVersion); - _async.updateVoxelSurfaceStyle(_voxelSurfaceStyle, _modelVersion); - } - - model::MeshPointer mesh = _mesh; - - // if (_meshVersion == 0) { - // return; // we have no mesh - // } - - if (!_pipeline) { - gpu::ShaderPointer vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(polyvox_vert))); - gpu::ShaderPointer pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(polyvox_frag))); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("xMap"), 0)); - slotBindings.insert(gpu::Shader::Binding(std::string("yMap"), 1)); - slotBindings.insert(gpu::Shader::Binding(std::string("zMap"), 2)); - - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); - gpu::Shader::makeProgram(*program, slotBindings); - - auto state = std::make_shared(); - state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); - } - - gpu::Batch& batch = *args->_batch; - batch.setPipeline(_pipeline); - - // _modelGeometryLock.lockForRead(); - // auto mesh = _modelGeometry.getMesh(); - - Transform transform(voxelToWorldMatrix()); - batch.setModelTransform(transform); - batch.setInputFormat(mesh->getVertexFormat()); - batch.setInputBuffer(gpu::Stream::POSITION, mesh->getVertexBuffer()); - batch.setInputBuffer(gpu::Stream::NORMAL, - mesh->getVertexBuffer()._buffer, - sizeof(float) * 3, - mesh->getVertexBuffer()._stride); - batch.setIndexBuffer(gpu::UINT32, mesh->getIndexBuffer()._buffer, 0); - - if (!_xTextureURL.isEmpty() && !_xTexture) { - _xTexture = DependencyManager::get()->getTexture(_xTextureURL); - } - if (!_yTextureURL.isEmpty() && !_yTexture) { - _yTexture = DependencyManager::get()->getTexture(_yTextureURL); - } - if (!_zTextureURL.isEmpty() && !_zTexture) { - _zTexture = DependencyManager::get()->getTexture(_zTextureURL); - } - - if (_xTexture) { - batch.setResourceTexture(0, _xTexture->getGPUTexture()); - } else { - batch.setResourceTexture(0, DependencyManager::get()->getWhiteTexture()); - } - if (_yTexture) { - batch.setResourceTexture(1, _yTexture->getGPUTexture()); - } else { - batch.setResourceTexture(1, DependencyManager::get()->getWhiteTexture()); - } - if (_zTexture) { - batch.setResourceTexture(2, _zTexture->getGPUTexture()); - } else { - batch.setResourceTexture(2, DependencyManager::get()->getWhiteTexture()); - } - - int voxelVolumeSizeLocation = _pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize"); - batch._glUniform3f(voxelVolumeSizeLocation, _voxelVolumeSize.x, _voxelVolumeSize.y, _voxelVolumeSize.z); - - batch.drawIndexed(gpu::TRIANGLES, mesh->getNumIndices(), 0); - // _modelGeometryLock.unlock(); - - RenderableDebugableEntityItem::render(this, args); -} - -bool RenderablePolyVoxEntityItem::addToScene(EntityItemPointer self, - std::shared_ptr scene, - render::PendingChanges& pendingChanges) { - _myItem = scene->allocateID(); - - auto renderItem = std::make_shared(shared_from_this()); - auto renderData = PolyVoxPayload::Pointer(renderItem); - auto renderPayload = std::make_shared(renderData); - - pendingChanges.resetItem(_myItem, renderPayload); - - return true; -} - -void RenderablePolyVoxEntityItem::removeFromScene(EntityItemPointer self, - std::shared_ptr scene, - render::PendingChanges& pendingChanges) { - pendingChanges.removeItem(_myItem); -} - -namespace render { - template <> const ItemKey payloadGetKey(const PolyVoxPayload::Pointer& payload) { - return ItemKey::Builder::opaqueShape(); - } - - template <> const Item::Bound payloadGetBound(const PolyVoxPayload::Pointer& payload) { - if (payload && payload->_owner) { - auto polyVoxEntity = std::dynamic_pointer_cast(payload->_owner); - return polyVoxEntity->getAABox(); - } - return render::Item::Bound(); - } - - template <> void payloadRender(const PolyVoxPayload::Pointer& payload, RenderArgs* args) { - if (args && payload && payload->_owner) { - payload->_owner->render(args); - } - } -} - -glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const { - return glm::vec3(voxelToWorldMatrix() * glm::vec4(voxelCoords, 1.0f)); -} - -glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3& worldCoords) const { - return glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); -} - -glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const { - return glm::vec3(voxelToLocalMatrix() * glm::vec4(voxelCoords, 0.0f)); -} - -glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& localCoords) const { - return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 0.0f)); -} - - -RenderablePolyVoxAsynchronous::RenderablePolyVoxAsynchronous(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, - glm::vec3 voxelVolumeSize, - RenderablePolyVoxEntityItem* owner) : - _voxelSurfaceStyle(voxelSurfaceStyle), - _onCount(0), - _owner(owner) { - setVoxelVolumeSize(voxelVolumeSize); -} - - -RenderablePolyVoxAsynchronous::~RenderablePolyVoxAsynchronous() { - _volDataLock.lockForWrite(); - delete _volData; - _volDataLock.unlock(); -} - - -void RenderablePolyVoxAsynchronous::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName(); - - _volDataLock.lockForWrite(); - _voxelVolumeSize = voxelVolumeSize; - - if (_volData) { - delete _volData; - } - _onCount = 0; - - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || - _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { - // 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); - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x - 1, // -1 because these corners are inclusive - _voxelVolumeSize.y - 1, - _voxelVolumeSize.z - 1); - _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(); -} - - -void RenderablePolyVoxAsynchronous::updateVoxelSurfaceStyle(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, - quint64 modelVersion) { - - // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName(); - - _volDataLock.lockForWrite(); - // if we are switching to or from "edged" we need to force a resize of _volData. - bool wasEdged = (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || - _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); - bool willBeEdged = (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || - voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES); - - if (wasEdged != willBeEdged) { - if (_volData) { - delete _volData; - } - _volData = nullptr; - _voxelSurfaceStyle = voxelSurfaceStyle; - _volDataLock.unlock(); - setVoxelVolumeSize(_voxelVolumeSize); -# ifdef THREAD_POLYVOX - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::decompressVolumeDataAsync, - _owner->getVoxelData(), modelVersion); -# else - decompressVolumeDataAsync(_owner->getVoxelData(), modelVersion); -# endif - } else { - _voxelSurfaceStyle = voxelSurfaceStyle; - _volDataLock.unlock(); -# ifdef THREAD_POLYVOX - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); -# else - getMeshAsync(modelVersion); -# endif - } -} - - - -bool RenderablePolyVoxAsynchronous::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. - switch (surfaceStyle) { - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: - case PolyVoxEntityItem::SURFACE_CUBIC: - if (x < 0 || y < 0 || z < 0 || - x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()) { - return false; - } - return true; - - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - if (x < 0 || y < 0 || z < 0 || - x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) { - return false; - } - return true; - } - - return false; -} - - -uint8_t RenderablePolyVoxAsynchronous::getVoxel(int x, int y, int z) { - _volDataLock.lockForRead(); - auto result = getVoxelInternal(x, y, z); - _volDataLock.unlock(); - return result; -} - -uint8_t RenderablePolyVoxAsynchronous::getVoxelInternal(int x, int y, int z) { - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return 0; - } - - // if _voxelSurfaceStyle is SURFACE_EDGED_CUBIC, we maintain an extra layer of - // voxels all around the requested voxel space. Having the empty voxels around - // the edges changes how the surface extractor behaves. - - uint8_t result; - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || - _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { - result = _volData->getVoxelAt(x + 1, y + 1, z + 1); - } else { - result = _volData->getVoxelAt(x, y, z); - } - - return result; -} - - -bool RenderablePolyVoxAsynchronous::setVoxelInternal(int x, int y, int z, uint8_t toValue) { - // set a voxel without recompressing the voxel data - bool result = false; - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return result; - } - - result = updateOnCount(x, y, z, toValue); - - assert(_volData); - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC || - _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { - _volData->setVoxelAt(x + 1, y + 1, z + 1, toValue); - } else { - _volData->setVoxelAt(x, y, z, toValue); - } - - return result; -} - - -bool RenderablePolyVoxAsynchronous::updateOnCount(int x, int y, int z, uint8_t toValue) { - // keep _onCount up to date - if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { - return false; - } - - uint8_t uVoxelValue = getVoxelInternal(x, y, z); - if (toValue != 0) { - if (uVoxelValue == 0) { - _onCount++; - return true; - } - } else { - // toValue == 0 - if (uVoxelValue != 0) { - _onCount--; - assert(_onCount >= 0); - return true; - } - } - return false; -} - - -bool RenderablePolyVoxAsynchronous::setVoxel(int x, int y, int z, uint8_t toValue, quint64 modelVersion) { - _volDataLock.lockForWrite(); - bool result = setVoxelInternal(x, y, z, toValue); - _volDataLock.unlock(); - if (result) { -# ifdef THREAD_POLYVOX - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::compressVolumeData, modelVersion); - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); -# else - compressVolumeData(modelVersion); - getMeshAsync(modelVersion); -# endif - } else { - // nothing changed. - // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); - _owner->setDataVersion(modelVersion); - } - return result; -} - - -bool RenderablePolyVoxAsynchronous::setAll(uint8_t toValue, quint64 modelVersion) { - bool result = false; - - _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++) { - result |= setVoxelInternal(x, y, z, toValue); - } - } - } - _volDataLock.unlock(); - if (result) { -# ifdef THREAD_POLYVOX - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::compressVolumeData, modelVersion); - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); -# else - compressVolumeData(modelVersion); - getMeshAsync(modelVersion); -# endif - } else { - // nothing changed. - // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); - _owner->setDataVersion(modelVersion); - } - return result; -} - - -bool RenderablePolyVoxAsynchronous::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue, quint64 modelVersion) { - bool result = false; - - // 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 make it solid. - if (fDistToCenter <= radius) { - result |= setVoxelInternal(x, y, z, toValue); - } - } - } - } - _volDataLock.unlock(); - if (result) { -# ifdef THREAD_POLYVOX - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::compressVolumeData, modelVersion); - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); -# else - compressVolumeData(modelVersion); - getMeshAsync(modelVersion); -# endif - } else { - // nothing changed. - // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); - _owner->setDataVersion(modelVersion); - } - - return result; -} - - -PolyVox::RaycastResult RenderablePolyVoxAsynchronous::doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, - glm::vec4& result) const { - 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(); - - // result is in voxel-space coordinates. - result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); - return raycastResult; -} - - -// take compressed data and expand it into _volData. -void RenderablePolyVoxAsynchronous::decompressVolumeData(QByteArray voxelData, quint64 modelVersion) { - // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName() << modelVersion; - -# ifdef THREAD_POLYVOX - QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::decompressVolumeDataAsync, voxelData, modelVersion); -# else - decompressVolumeDataAsync(voxelData, modelVersion); -# endif -} - - -void RenderablePolyVoxAsynchronous::decompressVolumeDataAsync(QByteArray voxelData, quint64 modelVersion) { - // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) - // << _owner->getName() << _owner->getID() << modelVersion; - - 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 << _owner->getName() << _owner->getID() << modelVersion - << "5d301155-faf9-44dd-8e8b-061a03d42c0f"; - // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); - _owner->setDataVersion(modelVersion); - 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() - << _owner->getName() << _owner->getID() << modelVersion << "5d301155-faf9-44dd-8e8b-061a03d42c0f"; - // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); - _owner->setDataVersion(modelVersion); - return; - } - - _volDataLock.lockForWrite(); - 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(); - - _owner->receiveNewVoxelData(voxelData, modelVersion); - -# ifdef THREAD_POLYVOX - QFuture future = QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::getMeshAsync, modelVersion); -# else - getMeshAsync(modelVersion); -# endif -} - - -void RenderablePolyVoxAsynchronous::getMeshAsync(quint64 modelVersion) { - // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName() << modelVersion; - - model::MeshPointer mesh(new model::Mesh()); - - // A mesh object to hold the result of surface extraction - PolyVox::SurfaceMesh polyVoxMesh; - - _volDataLock.lockForRead(); - switch (_voxelSurfaceStyle) { - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - case PolyVoxEntityItem::SURFACE_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - } - _volDataLock.unlock(); - - // 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))); - - // qDebug() << _owner->getID() << "OKOKOK -- MADE NEW MESH" << "index-count =" << vecIndices.size() - // << "vertex-count =" << vecVertices.size() << "mesh-vertg-size =" << mesh->getVertexBuffer()._size; - - _owner->receiveNewMesh(mesh, modelVersion); -} - -int RenderablePolyVoxAsynchronous::getOnCount() const { - // XXX get rid of this method - return _onCount; -} - - -// 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 RenderablePolyVoxAsynchronous::compressVolumeData(quint64 modelVersion) { - // qDebug() << _owner->getID() << "OKOKOK" << QString(__PRETTY_FUNCTION__) << _owner->getName() << modelVersion; - - quint16 voxelXSize = _voxelVolumeSize.x; - quint16 voxelYSize = _voxelVolumeSize.y; - quint16 voxelZSize = _voxelVolumeSize.z; - int rawSize = voxelXSize * voxelYSize * voxelZSize; - - 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) { - _owner->receiveNewVoxelData(newVoxelData, modelVersion); - } else { - // 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. - // QtConcurrent::run(this, &RenderablePolyVoxAsynchronous::decompressVolumeDataAsync, - // _owner->getVoxelData(), modelVersion); - // decompressVolumeDataAsync(_owner->getVoxelData(), modelVersion); - // _owner->receiveNewVoxelData(_owner->getVoxelData(), modelVersion); - _owner->setDataVersion(modelVersion); - } + + _meshLock.lockForWrite(); + _meshDirty = false; + _meshLock.unlock(); + _threadRunning.release(); + return; } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 405cdd07ff..33a5e86398 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -12,7 +12,7 @@ #ifndef hifi_RenderablePolyVoxEntityItem_h #define hifi_RenderablePolyVoxEntityItem_h -#include +#include #include #include @@ -41,56 +41,8 @@ namespace render { template <> void payloadRender(const PolyVoxPayload::Pointer& payload, RenderArgs* args); } -class RenderablePolyVoxEntityItem; - - -class RenderablePolyVoxAsynchronous : public QObject { - Q_OBJECT - - public: - RenderablePolyVoxAsynchronous(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, glm::vec3 voxelVolumeSize, - RenderablePolyVoxEntityItem* owner); - ~RenderablePolyVoxAsynchronous(); - - void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); - void updateVoxelSurfaceStyle(PolyVoxEntityItem::PolyVoxSurfaceStyle voxelSurfaceStyle, quint64 modelVersion); - uint8_t getVoxel(int x, int y, int z); - bool setVoxel(int x, int y, int z, uint8_t toValue, quint64 modelVersion); - bool setAll(uint8_t toValue, quint64 modelVersion); - bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue, quint64 modelVersion); - PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; - int getOnCount() const; - void decompressVolumeData(QByteArray voxelData, quint64 modelVersion); - -// signals: -// void doDecompressVolumeDataAsync(QByteArray voxelData, quint64 modelVersion); -// void doGetMeshAsync(quint64 modelVersion); - - - -private: - void decompressVolumeDataAsync(QByteArray voxelData, quint64 modelVersion); - void getMeshAsync(quint64 modelVersion); - - uint8_t getVoxelInternal(int x, int y, int z); - bool updateOnCount(int x, int y, int z, uint8_t new_value); - bool setVoxelInternal(int x, int y, int z, uint8_t toValue); - void compressVolumeData(quint64 modelVersion); - static bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z); - - PolyVoxEntityItem::PolyVoxSurfaceStyle _voxelSurfaceStyle; - glm::vec3 _voxelVolumeSize; - PolyVox::SimpleVolume* _volData = nullptr; - mutable QReadWriteLock _volDataLock; // lock for _volData - std::atomic_int _onCount; // how many non-zero voxels are in _volData - - RenderablePolyVoxEntityItem* _owner = nullptr; -}; - class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { - public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -153,21 +105,13 @@ class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { std::shared_ptr scene, render::PendingChanges& pendingChanges); - void receiveNewVoxelData(QByteArray newVoxelData, quint64 dataVersion); - void receiveNewMesh(model::MeshPointer newMeshPtr, quint64 meshVersion); - void setDataVersion(quint64 dataVersion) { _dataVersion = dataVersion; } - private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. - virtual bool computeShapeInfoWorker(); - - // model::Geometry _modelGeometry; - mutable QReadWriteLock _modelGeometryLock; model::MeshPointer _mesh; - - QFuture _getMeshWorker; + bool _meshDirty; // does collision-shape need to be recomputed? + mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive}; NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; @@ -178,17 +122,31 @@ private: static gpu::PipelinePointer _pipeline; ShapeInfo _shapeInfo; - // QFuture _shapeInfoWorker; mutable QReadWriteLock _shapeInfoLock; - // this does work outside of the main thread. - RenderablePolyVoxAsynchronous _async; + 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 - quint64 _modelVersion = 1; // local idea of how many changes have happened - // the following are compared against _modelVersion - quint64 _meshVersion = 0; // version of most recently computed mesh - quint64 _dataVersion = 0; // version of most recently compressed voxel data - quint64 _shapeVersion = 0; // version of most recently computed collision shape + bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z); + uint8_t getVoxelInternal(int x, int y, int z); + bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + 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(); + void computeShapeInfoWorker(); + void computeShapeInfoWorkerAsync(); + + QSemaphore _threadRunning{1}; }; diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index df3b6d1608..eb81c122e7 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -52,6 +52,7 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID, const Ent EntityItem(entityItemID), _voxelVolumeSize(PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE), _voxelData(PolyVoxEntityItem::DEFAULT_VOXEL_DATA), + _voxelDataDirty(true), _voxelSurfaceStyle(PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE), _xTextureURL(PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL), _yTextureURL(PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL), @@ -192,9 +193,13 @@ void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) _voxelDataLock.lockForWrite(); _voxelData = voxelData; + _voxelDataDirty = true; _voxelDataLock.unlock(); } const QByteArray PolyVoxEntityItem::getVoxelData() const { - return _voxelData; + _voxelDataLock.lockForRead(); + auto result = _voxelData; + _voxelDataLock.unlock(); + return result; } diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 118f6cc99b..c84dc9f4c1 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -105,8 +105,11 @@ class PolyVoxEntityItem : public EntityItem { protected: glm::vec3 _voxelVolumeSize; // this is always 3 bytes + mutable QReadWriteLock _voxelDataLock; QByteArray _voxelData; + bool _voxelDataDirty; + PolyVoxSurfaceStyle _voxelSurfaceStyle; QString _xTextureURL; From 3c35d90908a906b7c90f50f98dcf521c120ca4d4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 25 Aug 2015 13:13:01 -0700 Subject: [PATCH 10/29] don't recompute meshes if nothing changed --- .../src/RenderablePolyVoxEntityItem.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index c35a80f188..54ccab0c5e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -162,9 +162,13 @@ bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) _volDataLock.lockForWrite(); bool result = setVoxelInternal(x, y, z, toValue); - _volDataDirty = true; + if (result) { + _volDataDirty = true; + } _volDataLock.unlock(); - compressVolumeDataAndSendEditPacket(); + if (result) { + compressVolumeDataAndSendEditPacket(); + } auto timeSpent = usecTimestampNow() - now; qDebug() << "RenderablePolyVoxEntityItem::setVoxel timeSpent =" << timeSpent; @@ -188,7 +192,9 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { } } _volDataLock.unlock(); - compressVolumeDataAndSendEditPacket(); + if (result) { + compressVolumeDataAndSendEditPacket(); + } return result; } @@ -227,7 +233,9 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi } } _volDataLock.unlock(); - compressVolumeDataAndSendEditPacket(); + if (result) { + compressVolumeDataAndSendEditPacket(); + } return result; } From da6a1c958bdcfbc3c6e88934c0ecb7b496a5b72c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 25 Aug 2015 16:40:58 -0700 Subject: [PATCH 11/29] clean up debugging prints --- .../entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 4 +--- libraries/entities/src/PolyVoxEntityItem.cpp | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 54ccab0c5e..f220518e7e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -63,6 +63,7 @@ RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& ent _yTexture(nullptr), _zTexture(nullptr) { setVoxelVolumeSize(_voxelVolumeSize); + getMeshAsync(); } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { @@ -155,7 +156,6 @@ glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { - auto now = usecTimestampNow(); if (_locked) { return false; } @@ -170,8 +170,6 @@ bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) compressVolumeDataAndSendEditPacket(); } - auto timeSpent = usecTimestampNow() - now; - qDebug() << "RenderablePolyVoxEntityItem::setVoxel timeSpent =" << timeSpent; return result; } diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index eb81c122e7..d65e05464b 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -188,9 +188,6 @@ void PolyVoxEntityItem::debugDump() const { void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - qDebug() << "5d301155-faf9-44dd-8e8b-061a03d42c0f parent setVoxelData for" << getName() << getID() << ((void *)this) - << typeid(*this).name(); - _voxelDataLock.lockForWrite(); _voxelData = voxelData; _voxelDataDirty = true; From d2cfca0424843720a31be5194f1c89eb5915460b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 25 Aug 2015 16:41:10 -0700 Subject: [PATCH 12/29] fix comment typo --- examples/libraries/toolBars.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/libraries/toolBars.js b/examples/libraries/toolBars.js index abe8de8cc3..7ab68cab1c 100644 --- a/examples/libraries/toolBars.js +++ b/examples/libraries/toolBars.js @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Overlay2D = function(properties, overlay) { // overlay is an optionnal variable +Overlay2D = function(properties, overlay) { // overlay is an optional variable if (!(typeof(properties) === 'undefined')) { if(typeof(overlay) === 'undefined') { overlay = Overlays.addOverlay("image", properties); From 405c9828b8a50ad6e8cbf307957787272f635524 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 25 Aug 2015 16:41:38 -0700 Subject: [PATCH 13/29] cargo-cult a voxel-editor tool-bar from edit.js --- examples/voxels.js | 264 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 256 insertions(+), 8 deletions(-) diff --git a/examples/voxels.js b/examples/voxels.js index cc3453202a..893aa264d8 100644 --- a/examples/voxels.js +++ b/examples/voxels.js @@ -1,10 +1,233 @@ var controlHeld = false; var shiftHeld = false; + +Script.include([ + "libraries/toolBars.js", +]); + + +// http://headache.hungry.com/~seth/hifi/voxel-add.svg +// http://headache.hungry.com/~seth/hifi/voxel-add.svg +// http://headache.hungry.com/~seth/hifi/voxel-delete.svg +// http://headache.hungry.com/~seth/hifi/voxel-terrain.svg + +var isActive = false; +var toolIconUrl = "http://headache.hungry.com/~seth/hifi/"; +var toolHeight = 50; +var toolWidth = 50; + +var addingVoxels = false; +var deletingVoxels = false; + +offAlpha = 0.5; +onAlpha = 0.9; + + function floorVector(v) { return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)}; } +function vectorToString(v){ + return "{" + v.x + ", " + v.x + ", " + v.x + "}"; +} + +var toolBar = (function () { + var that = {}, + toolBar, + activeButton, + addVoxelButton, + deleteVoxelButton, + addTerrainButton; + + function initialize() { + toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.voxel.toolbar", function (windowDimensions, toolbar) { + return { + x: windowDimensions.x - 8*2 - toolbar.width * 2, + y: (windowDimensions.y - toolbar.height) / 2 + }; + }); + + activeButton = toolBar.addTool({ + imageURL: "http://s3.amazonaws.com/hifi-public/images/tools/polyvox.svg", + width: toolWidth, + height: toolHeight, + alpha: onAlpha, + visible: true, + }); + + addVoxelButton = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-add.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: offAlpha, + visible: false + }); + + deleteVoxelButton = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-delete.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: offAlpha, + visible: false + }); + + addTerrainButton = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-terrain.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: onAlpha, + visible: false + }); + + that.setActive(false); + } + + + that.setActive = function(active) { + if (active != isActive) { + isActive = active; + that.showTools(isActive); + } + toolBar.selectTool(activeButton, isActive); + }; + + // Sets visibility of tool buttons, excluding the power button + that.showTools = function(doShow) { + toolBar.showTool(addVoxelButton, doShow); + toolBar.showTool(deleteVoxelButton, doShow); + toolBar.showTool(addTerrainButton, doShow); + }; + + that.mousePressEvent = function (event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (activeButton === toolBar.clicked(clickedOverlay)) { + that.setActive(!isActive); + return true; + } + + if (addVoxelButton === toolBar.clicked(clickedOverlay)) { + if (addingVoxels) { + addingVoxels = false; + deletingVoxels = false; + toolBar.setAlpha(offAlpha, addVoxelButton); + toolBar.setAlpha(offAlpha, deleteVoxelButton); + toolBar.selectTool(addVoxelButton, false); + toolBar.selectTool(deleteVoxelButton, false); + } else { + addingVoxels = true; + deletingVoxels = false; + toolBar.setAlpha(onAlpha, addVoxelButton); + toolBar.setAlpha(offAlpha, deleteVoxelButton); + } + return true; + } + + if (deleteVoxelButton === toolBar.clicked(clickedOverlay)) { + if (deletingVoxels) { + deletingVoxels = false; + addingVoxels = false; + toolBar.setAlpha(offAlpha, addVoxelButton); + toolBar.setAlpha(offAlpha, deleteVoxelButton); + } else { + deletingVoxels = true; + addingVoxels = false; + toolBar.setAlpha(offAlpha, addVoxelButton); + toolBar.setAlpha(onAlpha, deleteVoxelButton); + } + return true; + } + + if (addTerrainButton === toolBar.clicked(clickedOverlay)) { + addTerrainBlock(); + return true; + } + } + + Window.domainChanged.connect(function() { + that.setActive(false); + }); + + that.cleanup = function () { + toolBar.cleanup(); + // Overlays.deleteOverlay(activeButton); + }; + + + initialize(); + return that; +}()); + + +function addTerrainBlock() { + + var myPosDiv16 = Vec3.multiply(Vec3.sum(MyAvatar.position, {x:8, x:8, z:8}), 1.0 / 16.0); + var myPosDiv16Floored = floorVector(myPosDiv16); + var baseLocation = Vec3.multiply(myPosDiv16Floored, 16.0); + + if (baseLocation.y + 8 > MyAvatar.position.y) { + baseLocation.y -= 16; + } + + print("myPosDiv16 is " + vectorToString(myPosDiv16)); + print("MyPosDiv16Floored is " + vectorToString(myPosDiv16Floored)); + print("baseLocation is " + vectorToString(baseLocation)); + + alreadyThere = Entities.findEntities(baseLocation, 1.0); + for (var i = 0; i < alreadyThere.length; i++) { + var id = alreadyThere[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == "terrain") { + print("already terrain there"); + return; + } + } + + var polyVoxId = Entities.addEntity({ + type: "PolyVox", + name: "terrain", + position: baseLocation, + dimensions: { x: 16, y: 16, z: 16 }, + voxelVolumeSize: {x:16, y:16, z:16}, + voxelSurfaceStyle: 3 + }); + Entities.setAllVoxels(polyVoxId, 255); + + for (var y = 8; y < 16; y++) { + for (var x = 0; x < 16; x++) { + for (var z = 0; z < 16; z++) { + Entities.setVoxel(polyVoxId, {x: x, y: y, z: z}, 0); + } + } + } + + // for (var x = 1; x <= 14; x++) { + // Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 1}, 255); + // Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 1}, 255); + // Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 14}, 255); + // Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 14}, 255); + // } + // for (var y = 2; y <= 13; y++) { + // Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 1}, 255); + // Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 1}, 255); + // Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 14}, 255); + // Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 14}, 255); + // } + // for (var z = 2; z <= 13; z++) { + // Entities.setVoxel(polyVoxId, {x: 1, y: 1, z: z}, 255); + // Entities.setVoxel(polyVoxId, {x: 14, y: 1, z: z}, 255); + // Entities.setVoxel(polyVoxId, {x: 1, y: 14, z: z}, 255); + // Entities.setVoxel(polyVoxId, {x: 14, y: 14, z: z}, 255); + // } + + return true; +} + + function attemptVoxelChange(pickRayDir, intersection) { var properties = Entities.getEntityProperties(intersection.entityID); @@ -12,25 +235,37 @@ function attemptVoxelChange(pickRayDir, intersection) { return false; } + if (addingVoxels == false && deletingVoxels == false) { + return false; + } + var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection); voxelPosition = Vec3.subtract(voxelPosition, {x: 0.5, y: 0.5, z: 0.5}); var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir); pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace); + var doAdd = addingVoxels; + var doDelete = deletingVoxels; + if (controlHeld) { - // hold control to erase a voxel + doAdd = deletingVoxels; + doDelete = addingVoxels; + } + + // } else if (shiftHeld) { + // // return Entities.setAllVoxels(intersection.entityID, 255); + // } + + // Entities.setVoxelSphere(id, intersection.intersection, radius, 0) + + if (doDelete) { var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); return Entities.setVoxel(intersection.entityID, floorVector(toErasePosition), 0); - } else if (shiftHeld) { - // hold shift to set all voxels to 255 - return Entities.setAllVoxels(intersection.entityID, 255); - } else { - // no modifier key to add a voxel + } + if (doAdd) { var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); return Entities.setVoxel(intersection.entityID, floorVector(toDrawPosition), 255); } - - // Entities.setVoxelSphere(id, intersection.intersection, radius, 0) } function mousePressEvent(event) { @@ -38,6 +273,10 @@ function mousePressEvent(event) { return; } + if (toolBar.mousePressEvent(event)) { + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking @@ -76,6 +315,15 @@ function keyReleaseEvent(event) { } +function cleanup() { + for (var i = 0; i < overlays.length; i++) { + Overlays.deleteOverlay(overlays[i]); + } + toolBar.cleanup(); +} + + Controller.mousePressEvent.connect(mousePressEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.scriptEnding.connect(cleanup); From 6f4683206936b124d092a4d5ed75e355f4a95a71 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 25 Aug 2015 21:30:38 -0700 Subject: [PATCH 14/29] clean up some commented code. attempt to fix ray-casting in non-edged polyvox -- still isn't correct --- examples/voxels.js | 35 +------------- .../src/RenderablePolyVoxEntityItem.cpp | 46 +++++++++++-------- 2 files changed, 29 insertions(+), 52 deletions(-) diff --git a/examples/voxels.js b/examples/voxels.js index 893aa264d8..4c344a7eee 100644 --- a/examples/voxels.js +++ b/examples/voxels.js @@ -1,17 +1,10 @@ var controlHeld = false; var shiftHeld = false; - Script.include([ "libraries/toolBars.js", ]); - -// http://headache.hungry.com/~seth/hifi/voxel-add.svg -// http://headache.hungry.com/~seth/hifi/voxel-add.svg -// http://headache.hungry.com/~seth/hifi/voxel-delete.svg -// http://headache.hungry.com/~seth/hifi/voxel-terrain.svg - var isActive = false; var toolIconUrl = "http://headache.hungry.com/~seth/hifi/"; var toolHeight = 50; @@ -23,7 +16,6 @@ var deletingVoxels = false; offAlpha = 0.5; onAlpha = 0.9; - function floorVector(v) { return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)}; } @@ -193,7 +185,7 @@ function addTerrainBlock() { position: baseLocation, dimensions: { x: 16, y: 16, z: 16 }, voxelVolumeSize: {x:16, y:16, z:16}, - voxelSurfaceStyle: 3 + voxelSurfaceStyle: 2 }); Entities.setAllVoxels(polyVoxId, 255); @@ -205,25 +197,6 @@ function addTerrainBlock() { } } - // for (var x = 1; x <= 14; x++) { - // Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 1}, 255); - // Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 1}, 255); - // Entities.setVoxel(polyVoxId, {x: x, y: 1, z: 14}, 255); - // Entities.setVoxel(polyVoxId, {x: x, y: 14, z: 14}, 255); - // } - // for (var y = 2; y <= 13; y++) { - // Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 1}, 255); - // Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 1}, 255); - // Entities.setVoxel(polyVoxId, {x: 1, y: y, z: 14}, 255); - // Entities.setVoxel(polyVoxId, {x: 14, y: y, z: 14}, 255); - // } - // for (var z = 2; z <= 13; z++) { - // Entities.setVoxel(polyVoxId, {x: 1, y: 1, z: z}, 255); - // Entities.setVoxel(polyVoxId, {x: 14, y: 1, z: z}, 255); - // Entities.setVoxel(polyVoxId, {x: 1, y: 14, z: z}, 255); - // Entities.setVoxel(polyVoxId, {x: 14, y: 14, z: z}, 255); - // } - return true; } @@ -252,12 +225,6 @@ function attemptVoxelChange(pickRayDir, intersection) { doDelete = addingVoxels; } - // } else if (shiftHeld) { - // // return Entities.setAllVoxels(intersection.entityID, 255); - // } - - // Entities.setVoxelSphere(id, intersection.intersection, radius, 0) - if (doDelete) { var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1)); return Entities.setVoxel(intersection.entityID, floorVector(toErasePosition), 0); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index f220518e7e..e986866bce 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -369,6 +369,34 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o return true; } + +PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel, + glm::vec4 farInVoxel, + glm::vec4& result) const { + 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(); + + // result is in voxel-space coordinates. + switch (_voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_CUBIC: + result = callback._result + glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); + break; + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); + break; + } + + return raycastResult; +} + + // virtual ShapeType RenderablePolyVoxEntityItem::getShapeType() const { return SHAPE_TYPE_COMPOUND; @@ -683,24 +711,6 @@ bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toV } -PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel, - glm::vec4 farInVoxel, - glm::vec4& result) const { - 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(); - - // result is in voxel-space coordinates. - result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); - return raycastResult; -} - - - void RenderablePolyVoxEntityItem::decompressVolumeData() { _threadRunning.acquire(); QtConcurrent::run(this, &RenderablePolyVoxEntityItem::decompressVolumeDataAsync); From b09de4ff653a7f50d25a1f4f25e59d89265ab851 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 26 Aug 2015 21:51:28 -0700 Subject: [PATCH 15/29] fix worldCoordsToVoxelCoords for non-edged voxels. voxel.js now adds/delete where you click rather than near to there. --- examples/voxels.js | 1 - .../src/RenderablePolyVoxEntityItem.cpp | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/examples/voxels.js b/examples/voxels.js index 4c344a7eee..d9049e2b0c 100644 --- a/examples/voxels.js +++ b/examples/voxels.js @@ -213,7 +213,6 @@ function attemptVoxelChange(pickRayDir, intersection) { } var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection); - voxelPosition = Vec3.subtract(voxelPosition, {x: 0.5, y: 0.5, z: 0.5}); var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir); pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index e986866bce..9f3d4faec5 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -382,17 +382,7 @@ PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originIn _volDataLock.unlock(); // result is in voxel-space coordinates. - switch (_voxelSurfaceStyle) { - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: - case PolyVoxEntityItem::SURFACE_CUBIC: - result = callback._result + glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); - break; - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: - result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); - break; - } - + result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f); return raycastResult; } @@ -559,7 +549,19 @@ glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3& voxel } glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3& worldCoords) const { - return glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); + glm::vec3 result = glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f)); + switch (_voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: + case PolyVoxEntityItem::SURFACE_CUBIC: + result += glm::vec3(0.5f, 0.5f, 0.5f); + break; + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: + result -= glm::vec3(0.5f, 0.5f, 0.5f); + break; + } + + return result; } glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const { @@ -754,6 +756,10 @@ void RenderablePolyVoxEntityItem::decompressVolumeDataAsync() { } _volDataLock.lockForWrite(); + if (!_volData) { + _volDataLock.unlock(); + return; + } _volDataDirty = true; for (int z = 0; z < voxelZSize; z++) { for (int y = 0; y < voxelYSize; y++) { From d3f06af892145b897507f0b0b46b4775267c5ddb Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 27 Aug 2015 18:22:12 -0700 Subject: [PATCH 16/29] remove PhysicsEntity class --- interface/src/avatar/SkeletonModel.cpp | 17 ++++---- libraries/animation/src/Rig.cpp | 23 ++++------- libraries/animation/src/Rig.h | 16 ++++---- libraries/render-utils/src/Model.cpp | 25 ++++++++---- libraries/render-utils/src/Model.h | 11 ++++- libraries/render-utils/src/PhysicsEntity.cpp | 34 ---------------- libraries/render-utils/src/PhysicsEntity.h | 42 -------------------- 7 files changed, 50 insertions(+), 118 deletions(-) delete mode 100644 libraries/render-utils/src/PhysicsEntity.cpp delete mode 100644 libraries/render-utils/src/PhysicsEntity.h diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 752fb55ce6..acd2f038f4 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -51,14 +51,14 @@ void SkeletonModel::initJointStates(QVector states) { int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1; int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1; - _boundingRadius = _rig->initJointStates(states, parentTransform, - rootJointIndex, - leftHandJointIndex, - leftElbowJointIndex, - leftShoulderJointIndex, - rightHandJointIndex, - rightElbowJointIndex, - rightShoulderJointIndex); + _rig->initJointStates(states, parentTransform, + rootJointIndex, + leftHandJointIndex, + leftElbowJointIndex, + leftShoulderJointIndex, + rightHandJointIndex, + rightElbowJointIndex, + rightShoulderJointIndex); // Determine the default eye position for avatar scale = 1.0 int headJointIndex = _geometry->getFBXGeometry().headJointIndex; @@ -533,7 +533,6 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition(); _boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition; - _boundingRadius = 0.5f * glm::length(diagonal); } void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3e466b94d6..8adb53024c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -185,14 +185,14 @@ void Rig::deleteAnimations() { _animationHandles.clear(); } -float Rig::initJointStates(QVector states, glm::mat4 parentTransform, - int rootJointIndex, - int leftHandJointIndex, - int leftElbowJointIndex, - int leftShoulderJointIndex, - int rightHandJointIndex, - int rightElbowJointIndex, - int rightShoulderJointIndex) { +void Rig::initJointStates(QVector states, glm::mat4 parentTransform, + int rootJointIndex, + int leftHandJointIndex, + int leftElbowJointIndex, + int leftShoulderJointIndex, + int rightHandJointIndex, + int rightElbowJointIndex, + int rightShoulderJointIndex) { _jointStates = states; _rootJointIndex = rootJointIndex; @@ -206,19 +206,12 @@ float Rig::initJointStates(QVector states, glm::mat4 parentTransform initJointTransforms(parentTransform); int numStates = _jointStates.size(); - float radius = 0.0f; for (int i = 0; i < numStates; ++i) { - float distance = glm::length(_jointStates[i].getPosition()); - if (distance > radius) { - radius = distance; - } _jointStates[i].buildConstraint(); } for (int i = 0; i < _jointStates.size(); i++) { _jointStates[i].slaveVisibleTransform(); } - - return radius; } // We could build and cache a dictionary, too.... diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 8da20062cf..a574807eb4 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -92,14 +92,14 @@ public: float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false); - float initJointStates(QVector states, glm::mat4 parentTransform, - int rootJointIndex, - int leftHandJointIndex, - int leftElbowJointIndex, - int leftShoulderJointIndex, - int rightHandJointIndex, - int rightElbowJointIndex, - int rightShoulderJointIndex); + void initJointStates(QVector states, glm::mat4 parentTransform, + int rootJointIndex, + int leftHandJointIndex, + int leftElbowJointIndex, + int leftShoulderJointIndex, + int rightHandJointIndex, + int rightElbowJointIndex, + int rightShoulderJointIndex); bool jointStatesEmpty() { return _jointStates.isEmpty(); }; int getJointStateCount() const { return _jointStates.size(); } int indexOfJoint(const QString& jointName) ; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c2d723a323..14b1c58321 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -60,6 +60,8 @@ float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; Model::Model(RigPointer rig, QObject* parent) : QObject(parent), + _translation(0.0f), + _rotation(), _scale(1.0f, 1.0f, 1.0f), _scaleToFit(false), _scaleToFitDimensions(0.0f), @@ -196,6 +198,13 @@ void Model::RenderPipelineLib::initLocations(gpu::ShaderPointer& program, Model: AbstractViewStateInterface* Model::_viewState = NULL; +void Model::setTranslation(const glm::vec3& translation) { + _translation = translation; +} + +void Model::setRotation(const glm::quat& rotation) { + _rotation = rotation; +} void Model::setScale(const glm::vec3& scale) { setScaleInternal(scale); @@ -434,14 +443,14 @@ void Model::initJointStates(QVector states) { int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1; int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1; - _boundingRadius = _rig->initJointStates(states, parentTransform, - rootJointIndex, - leftHandJointIndex, - leftElbowJointIndex, - leftShoulderJointIndex, - rightHandJointIndex, - rightElbowJointIndex, - rightShoulderJointIndex); + _rig->initJointStates(states, parentTransform, + rootJointIndex, + leftHandJointIndex, + leftElbowJointIndex, + leftShoulderJointIndex, + rightHandJointIndex, + rightElbowJointIndex, + rightShoulderJointIndex); } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index e55bff6aca..1a960f443b 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -27,7 +27,6 @@ #include #include #include -#include "PhysicsEntity.h" #include #include @@ -54,7 +53,7 @@ inline uint qHash(const std::shared_ptr& a, uint seed) { } /// A generic 3D model displaying geometry loaded from a URL. -class Model : public QObject, public PhysicsEntity { +class Model : public QObject { Q_OBJECT public: @@ -173,6 +172,12 @@ public: /// Returns the extents of the model's mesh Extents getMeshExtents() const; + void setTranslation(const glm::vec3& translation); + void setRotation(const glm::quat& rotation); + + const glm::vec3& getTranslation() const { return _translation; } + const glm::quat& getRotation() const { return _rotation; } + void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } @@ -233,6 +238,8 @@ protected: QSharedPointer _geometry; void setGeometry(const QSharedPointer& newGeometry); + glm::vec3 _translation; + glm::quat _rotation; glm::vec3 _scale; glm::vec3 _offset; diff --git a/libraries/render-utils/src/PhysicsEntity.cpp b/libraries/render-utils/src/PhysicsEntity.cpp deleted file mode 100644 index 5d58d87e84..0000000000 --- a/libraries/render-utils/src/PhysicsEntity.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// -// PhysicsEntity.cpp -// libraries/physics/src -// -// Created by Andrew Meadows 2014.06.11 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "PhysicsEntity.h" - -PhysicsEntity::PhysicsEntity() : - _translation(0.0f), - _rotation(), - _boundingRadius(0.0f) { -} - -PhysicsEntity::~PhysicsEntity() { -} - -void PhysicsEntity::setTranslation(const glm::vec3& translation) { - if (_translation != translation) { - _translation = translation; - } -} - -void PhysicsEntity::setRotation(const glm::quat& rotation) { - if (_rotation != rotation) { - _rotation = rotation; - } -} - diff --git a/libraries/render-utils/src/PhysicsEntity.h b/libraries/render-utils/src/PhysicsEntity.h deleted file mode 100644 index f36473af26..0000000000 --- a/libraries/render-utils/src/PhysicsEntity.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// PhysicsEntity.h -// libraries/physics/src -// -// Created by Andrew Meadows 2014.05.30 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_PhysicsEntity_h -#define hifi_PhysicsEntity_h - -#include -#include - -#include -#include - -class PhysicsEntity { - -public: - PhysicsEntity(); - virtual ~PhysicsEntity(); - - virtual void stepForward(float deltaTime) { } - - void setTranslation(const glm::vec3& translation); - void setRotation(const glm::quat& rotation); - - const glm::vec3& getTranslation() const { return _translation; } - const glm::quat& getRotation() const { return _rotation; } - float getBoundingRadius() const { return _boundingRadius; } - -protected: - glm::vec3 _translation; - glm::quat _rotation; - float _boundingRadius; -}; - -#endif // hifi_PhysicsEntity_h From 0bcd6b8ec505d2438c789472463e03c849edaeaf Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Aug 2015 10:30:56 -0700 Subject: [PATCH 17/29] remove no-longer-used #define --- libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 9f3d4faec5..199b7b90aa 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -45,8 +45,6 @@ #include "EntityEditPacketSender.h" #include "PhysicalEntitySimulation.h" -#define THREAD_POLYVOX 1 - gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; From 92b5fe34577ad4729f69cd462ba935269d94c71c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Aug 2015 10:31:28 -0700 Subject: [PATCH 18/29] fix thinko -- if the changes didn't happen, put them back on the list and don't clear their dirty flags --- libraries/physics/src/PhysicsEngine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index e8e6f68c6b..040055b313 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -195,11 +195,13 @@ VectorOfMotionStates PhysicsEngine::changeObjects(VectorOfMotionStates& objects) if (flags & HARD_DIRTY_PHYSICS_FLAGS) { if (object->handleHardAndEasyChanges(flags, this)) { object->clearIncomingDirtyFlags(); + } else { stillNeedChange.push_back(object); } } else if (flags & EASY_DIRTY_PHYSICS_FLAGS) { if (object->handleEasyChanges(flags, this)) { object->clearIncomingDirtyFlags(); + } else { stillNeedChange.push_back(object); } } From 1ac5c19f44741785a40a173b5eaa7ad42fb888ec Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Aug 2015 10:39:23 -0700 Subject: [PATCH 19/29] if someone else changes a polyvox, we need to localy recompute the collision shape --- libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 199b7b90aa..f03c80a36d 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -79,6 +79,7 @@ void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { _voxelDataDirty = true; _voxelDataLock.unlock(); decompressVolumeData(); + computeShapeInfoWorker(); } From a5cd3ff04603e0c6ee5653d695c046764f0fba0f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Aug 2015 11:04:56 -0700 Subject: [PATCH 20/29] release _threadRunning if decompressVolumeDataAsync exits early --- libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index f03c80a36d..258b4a600e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -757,6 +757,7 @@ void RenderablePolyVoxEntityItem::decompressVolumeDataAsync() { _volDataLock.lockForWrite(); if (!_volData) { _volDataLock.unlock(); + _threadRunning.release(); return; } _volDataDirty = true; From 540af4afcbdcbc24000a19e642da0e60e35d27b5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Aug 2015 12:47:04 -0700 Subject: [PATCH 21/29] remove unneeded call to computeShapeInfoWorker --- libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 258b4a600e..46c4986fa8 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -79,7 +79,6 @@ void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { _voxelDataDirty = true; _voxelDataLock.unlock(); decompressVolumeData(); - computeShapeInfoWorker(); } From 7614cababb950c87199a67762f7fd933733604e8 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Aug 2015 13:21:34 -0700 Subject: [PATCH 22/29] adjust whitespace --- libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 33a5e86398..110e8f8ab4 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -43,7 +43,7 @@ namespace render { class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { - public: +public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); RenderablePolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); From f6fbf4f3242aac0253c8960cc71c2f5f712476c3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Aug 2015 14:08:30 -0700 Subject: [PATCH 23/29] fix whitespace --- libraries/entities/src/PolyVoxEntityItem.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index d65e05464b..e765afd430 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -186,8 +186,7 @@ void PolyVoxEntityItem::debugDump() const { qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } -void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) -{ +void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) { _voxelDataLock.lockForWrite(); _voxelData = voxelData; _voxelDataDirty = true; From 38b62108afd49586e8cfca71ea9e44688005fc50 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 25 Aug 2015 18:54:48 -0700 Subject: [PATCH 24/29] Magnetic sticks and balls --- examples/toys/magSticks.js | 92 +++++ examples/toys/magSticks/constants.js | 16 + examples/toys/magSticks/handController.js | 94 +++++ examples/toys/magSticks/highlighter.js | 63 +++ examples/toys/magSticks/magBalls.js | 454 ++++++++++++++++++++++ examples/toys/magSticks/utils.js | 48 +++ 6 files changed, 767 insertions(+) create mode 100644 examples/toys/magSticks.js create mode 100644 examples/toys/magSticks/constants.js create mode 100644 examples/toys/magSticks/handController.js create mode 100644 examples/toys/magSticks/highlighter.js create mode 100644 examples/toys/magSticks/magBalls.js create mode 100644 examples/toys/magSticks/utils.js diff --git a/examples/toys/magSticks.js b/examples/toys/magSticks.js new file mode 100644 index 0000000000..7381754de0 --- /dev/null +++ b/examples/toys/magSticks.js @@ -0,0 +1,92 @@ +// +// Created by Bradley Austin Davis on 2015/08/25 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("magSticks/utils.js"); +Script.include("magSticks/constants.js"); +Script.include("magSticks/magBalls.js"); +Script.include("magSticks/highlighter.js"); +Script.include("magSticks/handController.js"); + +var magBalls = new MagBalls(); + +// Clear any previous balls +magBalls.clear(); + +// How close do we need to be to a ball to select it.... radius + 10% +var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.1; + +BallController = function(side) { + HandController.call(this, side); + this.highlighter = new Highlighter(); + this.highlighter.setSize(BALL_SIZE); + this.ghostEdges = {}; +} + +BallController.prototype = Object.create( HandController.prototype ); + +BallController.prototype.onUpdate = function(deltaTime) { + HandController.prototype.updateControllerState.call(this, deltaTime); + if (!this.selected) { + // Find the highlight target and set it. + var target = magBalls.findNearestBall(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(target); + return; + } + this.highlighter.highlight(null); + Entities.editEntity(this.selected, { position: this.tipPosition }); + var targetBalls = magBalls.findMatches(this.selected); + for (var ballId in targetBalls) { + if (!this.ghostEdges[ballId]) { + // create the ovleray + this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { + start: magBalls.getBallPosition(ballId), + end: this.tipPosition, + color: { red: 255, green: 0, blue: 0}, + alpha: 1, + lineWidth: 5, + visible: true, + }); + } else { + Overlays.editOverlay(this.ghostEdges[ballId], { + end: this.tipPosition, + }); + } + } + for (var ballId in this.ghostEdges) { + if (!targetBalls[ballId]) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } + } +} + +BallController.prototype.onClick = function() { + this.selected = magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(null); +} + +BallController.prototype.onRelease = function() { + this.clearGhostEdges(); + magBalls.releaseBall(this.selected); + this.selected = null; +} + +BallController.prototype.clearGhostEdges = function() { + for(var ballId in this.ghostEdges) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } +} + +BallController.prototype.onCleanup = function() { + HandController.prototype.updateControllerState.call(this); + this.clearGhostEdges(); +} + +// FIXME resolve some of the issues with dual controllers before allowing both controllers active +var handControllers = [new BallController(LEFT_CONTROLLER)]; //, new HandController(RIGHT) ]; diff --git a/examples/toys/magSticks/constants.js b/examples/toys/magSticks/constants.js new file mode 100644 index 0000000000..0453d29a79 --- /dev/null +++ b/examples/toys/magSticks/constants.js @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis on 2015/08/27 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx"; + +// FIXME make this editable through some script UI, so the user can customize the size of the structure built +SCALE = 0.5; +BALL_SIZE = 0.08 * SCALE; +STICK_LENGTH = 0.24 * SCALE; + diff --git a/examples/toys/magSticks/handController.js b/examples/toys/magSticks/handController.js new file mode 100644 index 0000000000..4f893c9cf2 --- /dev/null +++ b/examples/toys/magSticks/handController.js @@ -0,0 +1,94 @@ + +LEFT_CONTROLLER = 0; +RIGHT_CONTROLLER = 1; + + +HandController = function(side) { + this.side = side; + this.palm = 2 * side; + this.tip = 2 * side + 1; + this.action = findAction(side ? "ACTION2" : "ACTION1"); + this.active = false; + this.pointer = Overlays.addOverlay("sphere", { + position: { + x: 0, + y: 0, + z: 0 + }, + size: 0.01, + color: { + red: 255, + green: 255, + blue: 0 + }, + alpha: 1.0, + solid: true, + visible: false, + }); + + // Connect to desired events + var _this = this; + Controller.actionEvent.connect(function(action, state) { + _this.onActionEvent(action, state); + }); + + Script.update.connect(function(deltaTime) { + _this.onUpdate(deltaTime); + }); + + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); +} + +HandController.prototype.onActionEvent = function(action, state) { + if (action == this.action) { + if (state) { + this.onClick(); + } else { + this.onRelease(); + } + } +} + +HandController.prototype.setActive = function(active) { + if (active == this.active) { + return; + } + debugPrint("Setting active: " + active); + this.active = active; + Overlays.editOverlay(this.pointer, { + position: this.tipPosition, + visible: this.active + }); +} + +HandController.prototype.updateControllerState = function() { + var palmPos = Controller.getSpatialControlPosition(this.palm); + // When on the base hydras report a position of 0 + this.setActive(Vec3.length(palmPos) > 0.001); + if (!this.active) { + return; + } + var tipPos = Controller.getSpatialControlPosition(this.tip); + this.tipPosition = scaleLine(palmPos, tipPos, 1.4); + Overlays.editOverlay(this.pointer, { + position: this.tipPosition + }); +} + +HandController.prototype.onCleanup = function() { + Overlays.deleteOverlay(this.pointer); +} + +HandController.prototype.onUpdate = function(deltaTime) { + this.updateControllerState(); +} + +HandController.prototype.onClick = function() { + debugPrint("Base hand controller does nothing on click"); +} + +HandController.prototype.onRelease = function() { + debugPrint("Base hand controller does nothing on release"); +} diff --git a/examples/toys/magSticks/highlighter.js b/examples/toys/magSticks/highlighter.js new file mode 100644 index 0000000000..7316549c84 --- /dev/null +++ b/examples/toys/magSticks/highlighter.js @@ -0,0 +1,63 @@ +var SELECTION_OVERLAY = { + position: { + x: 0, + y: 0, + z: 0 + }, + color: { + red: 255, + green: 255, + blue: 0 + }, + alpha: 1, + size: 1.0, + solid: false, + //colorPulse: 1.0, + //pulseMin: 0.5, + //pulseMax: 1.0, + visible: false, + lineWidth: 1.0, + borderSize: 1.4, +}; + +Highlighter = function() { + this.highlightCube = Overlays.addOverlay("cube", this.SELECTION_OVERLAY); + this.hightlighted = null; + var _this = this; + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); +}; + +Highlighter.prototype.onCleanup = function() { + Overlays.deleteOverlay(this.highlightCube); +} + +Highlighter.prototype.highlight = function(entityId) { + if (entityId != this.hightlighted) { + this.hightlighted = entityId; + this.updateHighlight(); + } +} + +Highlighter.prototype.setSize = function(newSize) { + Overlays.editOverlay(this.highlightCube, { + size: newSize + }); +} + +Highlighter.prototype.updateHighlight = function() { + if (this.hightlighted) { + var properties = Entities.getEntityProperties(this.hightlighted); + // logDebug("Making highlight " + this.highlightCube + " visible @ " + vec3toStr(properties.position)); + Overlays.editOverlay(this.highlightCube, { + position: properties.position, + visible: true + }); + } else { + // logDebug("Making highlight invisible"); + Overlays.editOverlay(this.highlightCube, { + visible: false + }); + } +} \ No newline at end of file diff --git a/examples/toys/magSticks/magBalls.js b/examples/toys/magSticks/magBalls.js new file mode 100644 index 0000000000..9d67e0e0b7 --- /dev/null +++ b/examples/toys/magSticks/magBalls.js @@ -0,0 +1,454 @@ +var BALL_NAME = "MagBall" + +var EDGE_NAME = "MagStick" + +var BALL_DIMENSIONS = { + x: BALL_SIZE, + y: BALL_SIZE, + z: BALL_SIZE +}; + +var BALL_COLOR = { + red: 128, + green: 128, + blue: 128 +}; + +var STICK_DIMENSIONS = { + x: STICK_LENGTH / 6, + y: STICK_LENGTH / 6, + z: STICK_LENGTH +}; + +var BALL_DISTANCE = STICK_LENGTH + BALL_SIZE; + +var BALL_PROTOTYPE = { + type: "Sphere", + name: BALL_NAME, + dimensions: BALL_DIMENSIONS, + color: BALL_COLOR, + ignoreCollisions: true, + collisionsWillMove: false +}; + +// A collection of balls and edges connecting them. +MagBalls = function() { + /* + this.balls: { + ballId1: { + edgeId1: true + } + ballId2: { + edgeId1: true + }, + ballId3: { + edgeId2: true + edgeId3: true + edgeId4: true + edgeId5: true + }, + ... + } + */ + this.balls = {}; + /* + this.edges: { + edgeId1: { + ballId1: true + ballId2: true + }, + edgeId2: { + ballId3: true + ballId4: true + }, + ... + } + */ + + // FIXME initialize from nearby entities + this.edges = {}; + this.selectedBalls = {}; + + var _this = this; + Script.update.connect(function(deltaTime) { + _this.onUpdate(deltaTime); + }); + + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); +} + +MagBalls.prototype.findNearestBall = function(position, maxDist) { + var resultId = null; + var resultDist = 0; + for (var id in this.balls) { + var properties = Entities.getEntityProperties(id); + var curDist = Vec3.distance(properties.position, position); + if (!maxDist || curDist <= maxDist) { + if (!resultId || curDist < resultDist) { + resultId = id; + resultDist = curDist; + } + } + } + return resultId; +} + +// FIXME move to a physics based implementation as soon as bullet +// is exposed to entities +MagBalls.prototype.onUpdate = function(deltaTime) { +} + +function mergeObjects(proto, custom) { + var result = {}; + for (var attrname in proto) { + result[attrname] = proto[attrname]; + } + for (var attrname in custom) { + result[attrname] = custom[attrname]; + } + return result; +} + +MagBalls.prototype.createBall = function(customProperies) { + var ballId = Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies)); + this.balls[ballId] = {}; + this.validate(); + return ballId; +} + +MagBalls.prototype.grabBall = function(position, maxDist) { + var selected = this.findNearestBall(position, maxDist); + if (!selected) { + selected = this.createBall({ position: position }); + } + if (selected) { + this.breakEdges(selected); + this.selectedBalls[selected] = true; + } + return selected; +} + +MagBalls.prototype.findMatches = function(ballId) { + var variances = {}; + for (var otherBallId in this.balls) { + if (otherBallId == ballId || this.areConnected(otherBallId, ballId)) { + // can't self connect or doubly connect + continue; + } + var variance = this.getStickLengthVariance(ballId, otherBallId); + if (variance > BALL_DISTANCE / 4) { + continue; + } + variances[otherBallId] = variance; + } + return variances; +} + +MagBalls.prototype.releaseBall = function(releasedBall) { + delete this.selectedBalls[releasedBall]; + debugPrint("Released ball: " + releasedBall); + + // sort other balls by distance from the stick length + var edgeTargetBall = null; + do { + var releasePosition = this.getBallPosition(releasedBall); + // Get the list of candidate connections + var variances = this.findMatches(releasedBall); + // sort them by the difference from an ideal distance + var sortedBalls = Object.keys(variances); + if (!sortedBalls.length) { + return; + } + sortedBalls.sort(function(a, b){ + return variances[a] - variances[b]; + }); + // find the nearest matching unconnected ball + edgeTargetBall = sortedBalls[0]; + // note that createEdge will preferentially move the second parameter, the target ball + } while(this.createEdge(edgeTargetBall, releasedBall)) + + this.clean(); +} + +// FIXME the Quat should be able to do this +function findOrientation(from, to) { + //float m = sqrt(2.f + 2.f * dot(u, v)); + //vec3 w = (1.f / m) * cross(u, v); + //return quat(0.5f * m, w.x, w.y, w.z); + var v2 = Vec3.normalize(Vec3.subtract(to, from)); + var v1 = { x: 0.0, y: 1.0, z: 0.0 }; + var m = Math.sqrt(2 + 2 * Vec3.dot(v1, v2)); + var w = Vec3.multiply(1.0 / m, Vec3.cross(v1, v2)); + return { + w: 0.5 * m, + x: w.x, + y: w.y, + z: w.z + }; +} + +var LINE_DIMENSIONS = 5; + +var EDGE_PROTOTYPE = { + type: "Line", + name: EDGE_NAME, + color: { red: 0, green: 255, blue: 255 }, + dimensions: { + x: LINE_DIMENSIONS, + y: LINE_DIMENSIONS, + z: LINE_DIMENSIONS + }, + lineWidth: 5, + visible: true, + ignoreCollisions: true, + collisionsWillMove: false +} + +var ZERO_VECTOR = { + x: 0, + y: 0, + z: 0 +}; + +//var EDGE_PROTOTYPE = { +// type: "Sphere", +// name: EDGE_NAME, +// color: { red: 0, green: 255, blue: 255 }, +// //dimensions: STICK_DIMENSIONS, +// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, +// rotation: rotation, +// visible: true, +// ignoreCollisions: true, +// collisionsWillMove: false +//} + +MagBalls.prototype.createEdge = function(from, to) { + // FIXME find the constraints on from an to and determine if there is an intersection. + // Do only first order scanning for now, unless we can expose a mechanism for interacting + // to reach equilibrium via Bullet + // * a ball zero edges is fully free... + // * a ball with one edge free to move on a sphere section + // * a ball with two edges is free to move in a circle + // * a ball with more than two edges is not free + + var fromPos = this.getBallPosition(from); + var toPos = this.getBallPosition(to); + var vector = Vec3.subtract(toPos, fromPos); + var originalLength = Vec3.length(originalLength); + + // if they're already at a close enough distance, just create the edge + if ((originalLength - BALL_DISTANCE) > (BALL_DISTANCE * 0.01)) { + //code + } else { + // Attempt to move the ball to match the distance + vector = Vec3.multiply(BALL_DISTANCE, Vec3.normalize(vector)); + // Zero edges for the destination + var edgeCount = Object.keys(this.balls[to]).length; + if (!edgeCount) { + // update the entity + var newPosition = Vec3.sum(vector, fromPos); + Entities.editEntity(to, { position: newPosition }); + } else if (1 == edgeCount) { + // FIXME + // find the other end of the edge already connected, call it ball2 + // given two spheres of radius BALL_DISTANCE centered at fromPos and ball2.position, + // find the closest point of intersection to toPos + // move the ball to toPos + } else if (2 == edgeCount) { + // FIXME + } else { + // FIXME check for the ability to move fromPos + return false; + } + } + + + // Fixup existing edges + for (var edgeId in this.balls[to]) { + this.fixupEdge(edgeId); + } + + // FIXME, find the correct orientation for a box or model between the two balls + // for now use a line + var newEdge = Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, this.findEdgeParams(from, to))); + + this.edges[newEdge] = {}; + this.edges[newEdge][from] = true; + this.edges[newEdge][to] = true; + this.balls[from][newEdge] = true; + this.balls[to][newEdge] = true; + this.validate(); + return true; +} + +MagBalls.prototype.findEdgeParams = function(startBall, endBall) { + var startBallPos = this.getBallPosition(startBall); + var endBallPos = this.getBallPosition(endBall); + var vector = Vec3.subtract(endBallPos, startBallPos); + return { + position: startBallPos, + linePoints: [ ZERO_VECTOR, vector ] + }; +} + +MagBalls.prototype.fixupEdge = function(edgeId) { + var ballsInEdge = Object.keys(this.edges[edgeId]); + Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1])); +} + +// Given two balls, how big is the difference between their distances and the stick length +MagBalls.prototype.getStickLengthVariance = function(a, b) { + var apos = this.getBallPosition(a); + var bpos = this.getBallPosition(b); + var distance = Vec3.distance(apos, bpos); + var variance = Math.abs(distance - BALL_DISTANCE); + return variance; +} + +// FIXME remove unconnected balls +MagBalls.prototype.clean = function() { + //var deletedBalls = {}; + //if (Object.keys(this.balls).length > 1) { + // for (var ball in this.balls) { + // if (!this.getConnections(ball)) { + // deletedBalls[ball] = true; + // Entities.deleteEntity(ball); + // } + // } + //} + //for (var ball in deletedBalls) { + // delete this.balls[ball]; + //} +} + +MagBalls.prototype.getBallPosition = function(ball) { + var properties = Entities.getEntityProperties(ball); + return properties.position; +} + +MagBalls.prototype.breakEdges = function(ballId) { + for (var edgeId in this.balls[ballId]) { + this.destroyEdge(edgeId); + } + // This shouldn't be necessary + this.balls[ballId] = {}; +} + +MagBalls.prototype.destroyEdge = function(edgeId) { + logDebug("Deleting edge " + edgeId); + // Delete the edge from other balls + for (var edgeBallId in this.edges[edgeId]) { + delete this.balls[edgeBallId][edgeId]; + } + delete this.edges[edgeId]; + Entities.deleteEntity(edgeId); + this.validate(); +} + +MagBalls.prototype.destroyBall = function(ballId) { + logDebug("Deleting ball " + ballId); + breakEdges(ballId); + Entities.deleteEntity(ballId); +} + +MagBalls.prototype.clear = function() { + if (DEBUG_MAGSTICKS) { + var ids = Entities.findEntities(MyAvatar.position, 50); + var result = []; + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id); + if (properties.name == BALL_NAME || properties.name == EDGE_NAME) { + Entities.deleteEntity(id); + } + }, this); + } +} + +MagBalls.prototype.areConnected = function(a, b) { + for (var edge in this.balls[a]) { + // edge already exists + if (this.balls[b][edge]) { + return true; + } + } + return false; +} + +MagBalls.prototype.validate = function() { + var error = false; + for (ballId in this.balls) { + for (edgeId in this.balls[ballId]) { + var edge = this.edges[edgeId]; + if (!edge) { + logError("Error: ball " + ballId + " refers to unknown edge " + edgeId); + error = true; + continue; + } + if (!edge[ballId]) { + logError("Error: ball " + ballId + " refers to edge " + edgeId + " but not vice versa"); + error = true; + continue; + } + } + } + + for (edgeId in this.edges) { + for (ballId in this.edges[edgeId]) { + var ball = this.balls[ballId]; + if (!ball) { + logError("Error: edge " + edgeId + " refers to unknown ball " + ballId); + error = true; + continue; + } + if (!ball[edgeId]) { + logError("Error: edge " + edgeId + " refers to ball " + ballId + " but not vice versa"); + error = true; + continue; + } + } + } + if (error) { + logDebug(JSON.stringify({ edges: this.edges, balls: this.balls }, null, 2)); + } +} + +// FIXME fetch from a subkey of user data to support non-destructive modifications +MagBalls.prototype.setUserData = function(id, data) { + Entities.editEntity(id, { userData: JSON.stringify(data) }); +} + +// FIXME do non-destructive modification of the existing user data +MagBalls.prototype.getUserData = function(id) { + var results = null; + var properties = Entities.getEntityProperties(id); + if (properties.userData) { + results = JSON.parse(this.properties.userData); + } + return results; +} + +//MagBalls.prototype.findBalls = function() { +// var ids = Entities.findEntities(MyAvatar.position, 50); +// var result = []; +// ids.forEach(function(id) { +// var properties = Entities.getEntityProperties(id); +// if (properties.name == BALL_NAME) { +// result.push(id); +// } +// }, this); +// return result; +//}; +// +//MagBalls.prototype.findEdges = function() { +// var ids = Entities.findEntities(MyAvatar.position, 50); +// var result = []; +// ids.forEach(function(id) { +// var properties = Entities.getEntityProperties(id); +// if (properties.name == EDGE_NAME) { +// result.push(id); +// } +// }, this); +// return result; +//}; diff --git a/examples/toys/magSticks/utils.js b/examples/toys/magSticks/utils.js new file mode 100644 index 0000000000..43e4f800fb --- /dev/null +++ b/examples/toys/magSticks/utils.js @@ -0,0 +1,48 @@ + +DEBUG_MAGSTICKS = true; + +debugPrint = function (str) { + if (DEBUG_MAGSTICKS) { + print(str); + } +} + +vec3toStr = function (v) { + return "{ " + + (Math.round(v.x*1000)/1000) + ", " + + (Math.round(v.y*1000)/1000) + ", " + + (Math.round(v.z*1000)/1000) + " }"; +} + +scaleLine = function (start, end, scale) { + var v = Vec3.subtract(end, start); + var length = Vec3.length(v); + v = Vec3.multiply(scale, v); + return Vec3.sum(start, v); +} + +findAction = function(name) { + var actions = Controller.getAllActions(); + for (var i = 0; i < actions.length; i++) { + if (actions[i].actionName == name) { + return i; + } + } + return 0; +} + +logWarn = function(str) { + print(str); +} + +logError = function(str) { + print(str); +} + +logInfo = function(str) { + print(str); +} + +logDebug = function(str) { + debugPrint(str); +} \ No newline at end of file From ebb530aaace0ba8d03ea8d6307176cdfaf4463ab Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 28 Aug 2015 18:36:21 -0700 Subject: [PATCH 25/29] More magball work --- examples/toys/magSticks.js | 15 +- examples/toys/magSticks/constants.js | 52 ++ examples/toys/magSticks/graph.js | 270 +++++++ examples/toys/magSticks/handController.js | 65 +- examples/toys/magSticks/magBalls.js | 791 +++++++++++--------- examples/toys/magSticks/springEdgeEntity.js | 143 ++++ examples/toys/magSticks/utils.js | 91 ++- 7 files changed, 1049 insertions(+), 378 deletions(-) create mode 100644 examples/toys/magSticks/graph.js create mode 100644 examples/toys/magSticks/springEdgeEntity.js diff --git a/examples/toys/magSticks.js b/examples/toys/magSticks.js index 7381754de0..9461ffd047 100644 --- a/examples/toys/magSticks.js +++ b/examples/toys/magSticks.js @@ -6,8 +6,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("magSticks/utils.js"); Script.include("magSticks/constants.js"); +Script.include("magSticks/utils.js"); +Script.include("magSticks/graph.js"); Script.include("magSticks/magBalls.js"); Script.include("magSticks/highlighter.js"); Script.include("magSticks/handController.js"); @@ -18,7 +19,7 @@ var magBalls = new MagBalls(); magBalls.clear(); // How close do we need to be to a ball to select it.... radius + 10% -var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.1; +var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.5; BallController = function(side) { HandController.call(this, side); @@ -30,21 +31,21 @@ BallController = function(side) { BallController.prototype = Object.create( HandController.prototype ); BallController.prototype.onUpdate = function(deltaTime) { - HandController.prototype.updateControllerState.call(this, deltaTime); + HandController.prototype.onUpdate.call(this, deltaTime); if (!this.selected) { // Find the highlight target and set it. - var target = magBalls.findNearestBall(this.tipPosition, BALL_SELECTION_RADIUS); + var target = magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); this.highlighter.highlight(target); return; } this.highlighter.highlight(null); Entities.editEntity(this.selected, { position: this.tipPosition }); - var targetBalls = magBalls.findMatches(this.selected); + var targetBalls = magBalls.findPotentialEdges(this.selected); for (var ballId in targetBalls) { if (!this.ghostEdges[ballId]) { // create the ovleray this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { - start: magBalls.getBallPosition(ballId), + start: magBalls.getNodePosition(ballId), end: this.tipPosition, color: { red: 255, green: 0, blue: 0}, alpha: 1, @@ -84,7 +85,7 @@ BallController.prototype.clearGhostEdges = function() { } BallController.prototype.onCleanup = function() { - HandController.prototype.updateControllerState.call(this); + HandController.prototype.onCleanup.call(this); this.clearGhostEdges(); } diff --git a/examples/toys/magSticks/constants.js b/examples/toys/magSticks/constants.js index 0453d29a79..297fa51c6e 100644 --- a/examples/toys/magSticks/constants.js +++ b/examples/toys/magSticks/constants.js @@ -14,3 +14,55 @@ SCALE = 0.5; BALL_SIZE = 0.08 * SCALE; STICK_LENGTH = 0.24 * SCALE; +DEBUG_MAGSTICKS = true; + +ZERO_VECTOR = { x: 0, y: 0, z: 0 }; + +COLORS = { + WHITE: { + red: 255, + green: 255, + blue: 255, + }, + BLACK: { + red: 0, + green: 0, + blue: 0, + }, + GREY: { + red: 128, + green: 128, + blue: 128, + }, + RED: { + red: 255, + green: 0, + blue: 0 + }, + BLUE: { + red: 0, + green: 0, + blue: 255 + }, + GREEN: { + red: 0, + green: 255, + blue: 0 + }, + CYAN: { + red: 0, + green: 255, + blue: 255 + }, + YELLOW: { + red: 255, + green: 255, + blue: 0 + }, + MAGENTA: { + red: 255, + green: 0, + blue: 255 + } +} + diff --git a/examples/toys/magSticks/graph.js b/examples/toys/magSticks/graph.js new file mode 100644 index 0000000000..8a5c00451d --- /dev/null +++ b/examples/toys/magSticks/graph.js @@ -0,0 +1,270 @@ + +// A collection of nodes and edges connecting them. +Graph = function() { + /* Structure of nodes tree + this.nodes: { + nodeId1: { + edgeId1: true + } + nodeId2: { + edgeId1: true + }, + nodeId3: { + edgeId2: true + edgeId3: true + edgeId4: true + edgeId5: true + }, + ... + } + */ + this.nodes = {}; + /* Structure of edge tree + this.edges: { + edgeId1: { + // Every edge should have exactly two + nodeId1: true + nodeId2: true + }, + edgeId2: { + nodeId3: true + nodeId4: true + }, + ... + } + */ + this.edges = {}; +} + +Graph.prototype.createNodeEntity = function(properties) { + throw "Unimplemented"; +} + +Graph.prototype.createNode = function(properties) { + var nodeId = this.createNodeEntity(properties); + this.nodes[nodeId] = {}; + this.validate(); + return nodeId; +} + +Graph.prototype.createEdgeEntity = function(nodeA, nodeB) { + throw "Unimplemented"; +} + +Graph.prototype.createEdge = function(nodeA, nodeB) { + if (nodeA == nodeB) { + throw "Error: self connection not supported"; + } + var newEdgeId = this.createEdgeEntity(nodeA, nodeB); + + // Create the bidirectional linkage + this.edges[newEdgeId] = {}; + this.edges[newEdgeId][nodeA] = true; + this.edges[newEdgeId][nodeB] = true; + this.nodes[nodeA][newEdgeId] = true; + this.nodes[nodeB][newEdgeId] = true; + + this.validate(); +} + +Graph.prototype.getEdges = function(nodeId) { + var edges = this.nodes[nodeId]; + var result = {}; + for (var edgeId in edges) { + for (var otherNodeId in this.edges[edgeId]) { + if (otherNodeId != nodeId) { + result[edgeId] = otherNodeId; + } + } + } + return result; +} + +Graph.prototype.getConnectedNodes = function(nodeId) { + var edges = this.getEdges(nodeId); + var result = {}; + for (var edgeId in edges) { + var otherNodeId = edges[edgeId]; + result[otherNodeId] = edgeId; + } + return result; +} + +Graph.prototype.getEdgeLength = function(edgeId) { + var nodesInEdge = Object.keys(this.edges[edgeId]); + return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]); +} + +Graph.prototype.getNodeDistance = function(a, b) { + var apos = this.getNodePosition(a); + var bpos = this.getNodePosition(b); + return Vec3.distance(apos, bpos); +} + +Graph.prototype.getNodePosition = function(node) { + var properties = Entities.getEntityProperties(node); + return properties.position; +} + +Graph.prototype.breakEdges = function(nodeId) { + for (var edgeId in this.nodes[nodeId]) { + this.destroyEdge(edgeId); + } +} + +Graph.prototype.findNearestNode = function(position, maxDist) { + var resultId = null; + var resultDist = 0; + for (var nodeId in this.nodes) { + var nodePosition = this.getNodePosition(nodeId); + var curDist = Vec3.distance(nodePosition, position); + if (!maxDist || curDist <= maxDist) { + if (!resultId || curDist < resultDist) { + resultId = nodeId; + resultDist = curDist; + } + } + } + return resultId; +} + +Graph.prototype.findMatchingNodes = function(selector) { + var result = {}; + for (var nodeId in this.nodes) { + if (selector(nodeId)) { + result[nodeId] = true; + } + } + return result; +} + +Graph.prototype.destroyEdge = function(edgeId) { + logDebug("Deleting edge " + edgeId); + for (var nodeId in this.edges[edgeId]) { + delete this.nodes[nodeId][edgeId]; + } + delete this.edges[edgeId]; + Entities.deleteEntity(edgeId); + this.validate(); +} + +Graph.prototype.destroyNode = function(nodeId) { + logDebug("Deleting node " + nodeId); + this.breakEdges(nodeId); + delete this.nodes[nodeId]; + Entities.deleteEntity(nodeId); + this.validate(); +} + +Graph.prototype.deleteAll = function() { + var nodeIds = Object.keys(this.nodes); + for (var i in nodeIds) { + var nodeId = nodeIds[i]; + this.destroyNode(nodeId); + } +} + +Graph.prototype.areConnected = function(nodeIdA, nodeIdB) { + for (var edgeId in this.nodes[nodeIdA]) { + if (this.nodes[nodeIdB][edgeId]) { + return true; + } + } + return false; +} + +forEachValue = function(val, operation) { + if( typeof val === 'string' ) { + operation(val); + } else if (typeof val === 'object') { + if (val.constructor === Array) { + for (var i in val) { + operation(val[i]); + } + } else { + for (var v in val) { + operation(v); + } + } + } +} + +Graph.prototype.findShortestPath = function(start, end, options) { + var queue = [ start ]; + var prev = {}; + if (options && options.exclude) { + forEachValue(options.exclude, function(value) { + prev[value] = value; + }); + logDebug("exclude " + prev); + } + var found = false; + while (!found && Object.keys(queue).length) { + var current = queue.shift(); + for (var ballId in this.getConnectedNodes(current)) { + if (prev[ballId]) { + // already visited node + continue; + } + // record optimal path + prev[ballId] = current; + if (ballId == end) { + found = true; + break; + } + queue.push(ballId); + } + } + + if (!found) { + logDebug("Exhausted search"); + return; + } + + var result = [ end ]; + while (result[0] != start) { + result.unshift(prev[result[0]]); + } + + return result; +} + +Graph.prototype.validate = function() { + var error = false; + for (nodeId in this.nodes) { + for (edgeId in this.nodes[nodeId]) { + var edge = this.edges[edgeId]; + if (!edge) { + logError("Error: node " + nodeId + " refers to unknown edge " + edgeId); + error = true; + continue; + } + if (!edge[nodeId]) { + logError("Error: node " + nodeId + " refers to edge " + edgeId + " but not vice versa"); + error = true; + continue; + } + } + } + + for (edgeId in this.edges) { + for (nodeId in this.edges[edgeId]) { + var node = this.nodes[nodeId]; + if (!node) { + logError("Error: edge " + edgeId + " refers to unknown node " + nodeId); + error = true; + continue; + } + if (!node[edgeId]) { + logError("Error: edge " + edgeId + " refers to node " + nodeId + " but not vice versa"); + error = true; + continue; + } + } + } + + if (error) { + logDebug(JSON.stringify({ edges: this.edges, balls: this.nodes }, null, 2)); + } + return error; +} \ No newline at end of file diff --git a/examples/toys/magSticks/handController.js b/examples/toys/magSticks/handController.js index 4f893c9cf2..cf3ef96b4a 100644 --- a/examples/toys/magSticks/handController.js +++ b/examples/toys/magSticks/handController.js @@ -2,6 +2,26 @@ LEFT_CONTROLLER = 0; RIGHT_CONTROLLER = 1; +WAND_LIFETIME = 30; + +var WAND_PROPERTIES = { + type: "Model", + modelURL: "file:///f:/Downloads/wand.FBX", + ignoreForCollisions: true, + dimensions: { + x: 0.1, + y: 0.1, + z: 0.1 + }, + lifetime: 30 +}; + +if (!Date.now) { + Date.now = function now() { + return new Date().getTime(); + }; +} + HandController = function(side) { this.side = side; @@ -9,22 +29,17 @@ HandController = function(side) { this.tip = 2 * side + 1; this.action = findAction(side ? "ACTION2" : "ACTION1"); this.active = false; + this.tipScale = 1.4; this.pointer = Overlays.addOverlay("sphere", { - position: { - x: 0, - y: 0, - z: 0 - }, + position: ZERO_VECTOR, size: 0.01, - color: { - red: 255, - green: 255, - blue: 0 - }, + color: COLORS.YELLOW, alpha: 1.0, solid: true, visible: false, }); + //this.wand = Entities.addEntity(WAND_PROPERTIES); + // Connect to desired events var _this = this; @@ -55,26 +70,26 @@ HandController.prototype.setActive = function(active) { if (active == this.active) { return; } - debugPrint("Setting active: " + active); + logDebug("Setting active: " + active); this.active = active; Overlays.editOverlay(this.pointer, { - position: this.tipPosition, + visible: this.active + }); + Entities.editEntity(this.wand, { visible: this.active }); } HandController.prototype.updateControllerState = function() { - var palmPos = Controller.getSpatialControlPosition(this.palm); + this.palmPos = Controller.getSpatialControlPosition(this.palm); + var tipPos = Controller.getSpatialControlPosition(this.tip); + this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale); + // When on the base hydras report a position of 0 - this.setActive(Vec3.length(palmPos) > 0.001); + this.setActive(Vec3.length(this.palmPos) > 0.001); if (!this.active) { return; } - var tipPos = Controller.getSpatialControlPosition(this.tip); - this.tipPosition = scaleLine(palmPos, tipPos, 1.4); - Overlays.editOverlay(this.pointer, { - position: this.tipPosition - }); } HandController.prototype.onCleanup = function() { @@ -83,12 +98,20 @@ HandController.prototype.onCleanup = function() { HandController.prototype.onUpdate = function(deltaTime) { this.updateControllerState(); + if (this.active) { + Overlays.editOverlay(this.pointer, { + position: this.tipPosition + }); + Entities.editEntity(this.wand, { + position: this.tipPosition + }); + } } HandController.prototype.onClick = function() { - debugPrint("Base hand controller does nothing on click"); + logDebug("Base hand controller does nothing on click"); } HandController.prototype.onRelease = function() { - debugPrint("Base hand controller does nothing on release"); + logDebug("Base hand controller does nothing on release"); } diff --git a/examples/toys/magSticks/magBalls.js b/examples/toys/magSticks/magBalls.js index 9d67e0e0b7..9a0465c582 100644 --- a/examples/toys/magSticks/magBalls.js +++ b/examples/toys/magSticks/magBalls.js @@ -1,5 +1,5 @@ -var BALL_NAME = "MagBall" +var BALL_NAME = "MagBall" var EDGE_NAME = "MagStick" var BALL_DIMENSIONS = { @@ -31,44 +31,48 @@ var BALL_PROTOTYPE = { collisionsWillMove: false }; +// 2 millimeters +var EPSILON = (.002) / BALL_DISTANCE; + +var LINE_DIMENSIONS = { + x: 5, + y: 5, + z: 5 +} + +var LINE_PROTOTYPE = { + type: "Line", + name: EDGE_NAME, + color: COLORS.CYAN, + dimensions: LINE_DIMENSIONS, + lineWidth: 5, + visible: true, + ignoreCollisions: true, + collisionsWillMove: false, + script: "file:/Users/bdavis/Git/hifi/examples/toys/magSticks/springEdgeEntity.js" +} + +var EDGE_PROTOTYPE = LINE_PROTOTYPE; + +// var EDGE_PROTOTYPE = { +// type: "Sphere", +// name: EDGE_NAME, +// color: { red: 0, green: 255, blue: 255 }, +// //dimensions: STICK_DIMENSIONS, +// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, +// rotation: rotation, +// visible: true, +// ignoreCollisions: true, +// collisionsWillMove: false +// } + + // A collection of balls and edges connecting them. MagBalls = function() { - /* - this.balls: { - ballId1: { - edgeId1: true - } - ballId2: { - edgeId1: true - }, - ballId3: { - edgeId2: true - edgeId3: true - edgeId4: true - edgeId5: true - }, - ... - } - */ - this.balls = {}; - /* - this.edges: { - edgeId1: { - ballId1: true - ballId2: true - }, - edgeId2: { - ballId3: true - ballId4: true - }, - ... - } - */ - - // FIXME initialize from nearby entities - this.edges = {}; - this.selectedBalls = {}; - + this.selectedNodes = {}; + + Graph.call(this); + var _this = this; Script.update.connect(function(deltaTime) { _this.onUpdate(deltaTime); @@ -79,282 +83,463 @@ MagBalls = function() { }); } -MagBalls.prototype.findNearestBall = function(position, maxDist) { - var resultId = null; - var resultDist = 0; - for (var id in this.balls) { - var properties = Entities.getEntityProperties(id); - var curDist = Vec3.distance(properties.position, position); - if (!maxDist || curDist <= maxDist) { - if (!resultId || curDist < resultDist) { - resultId = id; - resultDist = curDist; - } - } - } - return resultId; -} +MagBalls.prototype = Object.create( Graph.prototype ); -// FIXME move to a physics based implementation as soon as bullet -// is exposed to entities MagBalls.prototype.onUpdate = function(deltaTime) { + // FIXME move to a physics based implementation as soon as bullet + // is exposed to entities } -function mergeObjects(proto, custom) { - var result = {}; - for (var attrname in proto) { - result[attrname] = proto[attrname]; - } - for (var attrname in custom) { - result[attrname] = custom[attrname]; - } - return result; +MagBalls.prototype.createNodeEntity = function(customProperies) { + return Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies)); } -MagBalls.prototype.createBall = function(customProperies) { - var ballId = Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies)); - this.balls[ballId] = {}; - this.validate(); - return ballId; +MagBalls.prototype.getEdgeProperties = function(nodeIdA, nodeIdB) { + var apos = this.getNodePosition(nodeIdA); + var bpos = this.getNodePosition(nodeIdB); + return { + position: apos, + linePoints: [ ZERO_VECTOR, Vec3.subtract(bpos, apos) ], + userData: JSON.stringify({ + magBalls: { + start: nodeIdA, + end: nodeIdB, + length: BALL_DISTANCE + } + }) + }; } -MagBalls.prototype.grabBall = function(position, maxDist) { - var selected = this.findNearestBall(position, maxDist); - if (!selected) { - selected = this.createBall({ position: position }); - } - if (selected) { - this.breakEdges(selected); - this.selectedBalls[selected] = true; - } - return selected; +MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) { + var customProperties = this.getEdgeProperties(nodeIdA, nodeIdB) + return Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, customProperties)); } -MagBalls.prototype.findMatches = function(ballId) { +MagBalls.prototype.findPotentialEdges = function(nodeId) { var variances = {}; - for (var otherBallId in this.balls) { - if (otherBallId == ballId || this.areConnected(otherBallId, ballId)) { - // can't self connect or doubly connect + for (var otherNodeId in this.nodes) { + // can't self connect + if (otherNodeId == nodeId) { continue; } - var variance = this.getStickLengthVariance(ballId, otherBallId); - if (variance > BALL_DISTANCE / 4) { + + // can't doubly connect + if (this.areConnected(otherNodeId, nodeId)) { continue; } - variances[otherBallId] = variance; + + // Too far to attempt + var distance = this.getNodeDistance(nodeId, otherNodeId); + var variance = this.getVariance(distance); + if (variance > 0.25) { + continue; + } + + variances[otherNodeId] = variance; } return variances; } -MagBalls.prototype.releaseBall = function(releasedBall) { - delete this.selectedBalls[releasedBall]; - debugPrint("Released ball: " + releasedBall); +MagBalls.prototype.grabBall = function(position, maxDist) { + var selected = this.findNearestNode(position, maxDist); + if (!selected) { + selected = this.createNode({ position: position }); + } + if (selected) { + this.breakEdges(selected); + this.selectedNodes[selected] = true; + } + return selected; +} - // sort other balls by distance from the stick length - var edgeTargetBall = null; - do { - var releasePosition = this.getBallPosition(releasedBall); - // Get the list of candidate connections - var variances = this.findMatches(releasedBall); - // sort them by the difference from an ideal distance - var sortedBalls = Object.keys(variances); - if (!sortedBalls.length) { +MagBalls.prototype.releaseBall = function(releasedBall) { + delete this.selectedNodes[releasedBall]; + logDebug("Released ball: " + releasedBall); + + // FIXME iterate through the other balls and ensure we don't intersect with + // any of them. If we do, just delete this ball and return. (play a pop + // sound) + + var releasePosition = this.getNodePosition(releasedBall); + + // Don't overlap other balls + for (var nodeId in this.nodes) { + if (nodeId == releasedBall) { + continue; + } + var distance = this.getNodeDistance(releasedBall, nodeId); + if (distance < BALL_SIZE / 2.0) { + this.destroyNode(nodeId); return; } - sortedBalls.sort(function(a, b){ + } + + var targets = this.findPotentialEdges(releasedBall); + for (var otherBallId in targets) { + this.createEdge(otherBallId, releasedBall); + } + this.clean(); + return; + + // sort other balls by distance from the stick length + var createdEdge = false; + while (true) { + // Get the list of candidate connections + var variances = this.findPotentialEdges(releasedBall); + // sort them by the difference from an ideal distance + var targetBalls = Object.keys(variances); + if (!targetBalls.length) { + break; + } + + // special case when there are 2 potential matches, + // try to create a ring on a plane + if (targetBalls.length == 2 && this.tryCreateRing(targetBalls, releasedBall)) { + createdEdge = true; + break; + } + + // special case when there are 3 potential matches, + // try create a fan + if (targetBalls.length == 3 && this.tryCreateFan(targetBalls, releasedBall)) { + createdEdge = true; + break; + } + + targetBalls.sort(function(a, b){ return variances[a] - variances[b]; }); - // find the nearest matching unconnected ball - edgeTargetBall = sortedBalls[0]; - // note that createEdge will preferentially move the second parameter, the target ball - } while(this.createEdge(edgeTargetBall, releasedBall)) + + // find the nearest matching unconnected ball and create an edge to it + // if possible + // note that createEdge will preferentially move the second entity, the + // released ball + // in order to create a fit + var str = "Attempt to create edge between " + targetBalls[0] + " and " + releasedBall; + if (!this.tryCreateEdge(targetBalls[0], releasedBall)) { + logDebug(str + "failed"); + var nodeDistance = this.getNodeDistance(targetBalls[0], releasedBall); + var variance = this.getVariance(nodeDistance); + logDebug("Distance was " + (nodeDistance * 100).toFixed(2) + "cm with a variance of " + variance); + break; + } + logDebug(str + " succeeded"); + + releasePosition = this.getNodePosition(releasedBall); + + // Record that we created at least one edge + createdEdge = true; + } + + if (createdEdge) { + // FIXME play a snap sound + } this.clean(); + this.validate(); } -// FIXME the Quat should be able to do this -function findOrientation(from, to) { - //float m = sqrt(2.f + 2.f * dot(u, v)); - //vec3 w = (1.f / m) * cross(u, v); - //return quat(0.5f * m, w.x, w.y, w.z); - var v2 = Vec3.normalize(Vec3.subtract(to, from)); - var v1 = { x: 0.0, y: 1.0, z: 0.0 }; - var m = Math.sqrt(2 + 2 * Vec3.dot(v1, v2)); - var w = Vec3.multiply(1.0 / m, Vec3.cross(v1, v2)); - return { - w: 0.5 * m, - x: w.x, - y: w.y, - z: w.z - }; -} - -var LINE_DIMENSIONS = 5; - -var EDGE_PROTOTYPE = { - type: "Line", - name: EDGE_NAME, - color: { red: 0, green: 255, blue: 255 }, - dimensions: { - x: LINE_DIMENSIONS, - y: LINE_DIMENSIONS, - z: LINE_DIMENSIONS - }, - lineWidth: 5, - visible: true, - ignoreCollisions: true, - collisionsWillMove: false -} - -var ZERO_VECTOR = { - x: 0, - y: 0, - z: 0 -}; - -//var EDGE_PROTOTYPE = { -// type: "Sphere", -// name: EDGE_NAME, -// color: { red: 0, green: 255, blue: 255 }, -// //dimensions: STICK_DIMENSIONS, -// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, -// rotation: rotation, -// visible: true, -// ignoreCollisions: true, -// collisionsWillMove: false -//} - -MagBalls.prototype.createEdge = function(from, to) { - // FIXME find the constraints on from an to and determine if there is an intersection. - // Do only first order scanning for now, unless we can expose a mechanism for interacting - // to reach equilibrium via Bullet - // * a ball zero edges is fully free... - // * a ball with one edge free to move on a sphere section - // * a ball with two edges is free to move in a circle - // * a ball with more than two edges is not free - - var fromPos = this.getBallPosition(from); - var toPos = this.getBallPosition(to); +MagBalls.prototype.tryCreateEdge = function(from, to) { + var fromPos = this.getNodePosition(from); + var toPos = this.getNodePosition(to); var vector = Vec3.subtract(toPos, fromPos); - var originalLength = Vec3.length(originalLength); - + var originalLength = Vec3.length(vector); + + var variance = this.getVariance(originalLength); // if they're already at a close enough distance, just create the edge - if ((originalLength - BALL_DISTANCE) > (BALL_DISTANCE * 0.01)) { - //code - } else { - // Attempt to move the ball to match the distance + if (variance < EPSILON) { + logDebug("Length " + originalLength + " with variance of " + (variance * 100).toFixed(2) + " is within epislon " + EPSILON) ; + // close enough for government work + this.createEdge(from, to); + return true; + } + + // FIXME find the constraints on `from` and `to` and determine if there is a + // new positiong + // for 'to' that keeps it's current connections and connects with 'from' + // Do only first order scanning for now, unless we can expose a mechanism + // for interacting + // to reach equilibrium via Bullet + // * a ball zero edges is fully free... + // * a ball with one edge free to move on a sphere section + // * a ball with two edges is free to move in a circle + // * a ball with more than two edges is not free to move + + // Zero edges for the destination + var existingEdges = Object.keys(this.nodes[to]); + var edgeCount = existingEdges.length; + if (!edgeCount) { + // Easy case 1: unconnected ball + // Move the ball along it's current path to match the desired distance vector = Vec3.multiply(BALL_DISTANCE, Vec3.normalize(vector)); - // Zero edges for the destination - var edgeCount = Object.keys(this.balls[to]).length; - if (!edgeCount) { + // Add the vector to the starting position to find the new position + var newPosition = Vec3.sum(vector, fromPos); + // update the entity + Entities.editEntity(to, { position: newPosition }); + moved = true; + } else if (edgeCount > 2) { + // Easy case 2: locked position ball + // FIXME should check the target ball to see if it can be moved. + // Possible easy solution is to recurse into this.createEdge and swap + // the parameters, + // but need to prevert infinite recursion + // for now... + return false; + } else { + var connectedBalls = this.getConnectedNodes(to); + // find the other balls connected, will be either 1 or 2 + var origin = { x: 0, y: 0, z: 0 }; + for (var nodeId in connectedBalls) { + origin = Vec3.sum(origin, this.getNodePosition(nodeId)); + } + + if (edgeCount > 1) { + origin = Vec3.multiply(origin, 1 / edgeCount); + } + // logDebug("Using origin " + vec3toStr(origin)); + + if (edgeCount == 1) { + // vectors from the temp origin to the two balls. + var v1 = Vec3.subtract(toPos, origin); + var v2 = Vec3.subtract(fromPos, origin); + + // ortogonal to the solution plane + var o1 = Vec3.normalize(Vec3.cross(Vec3.normalize(v2), Vec3.normalize(v1))); + // addLine(origin, o1, COLORS.RED); // debugging + + // orthogonal to o1, lying on the solution plane + var o2 = Vec3.normalize(Vec3.cross(o1, Vec3.normalize(v2))); + // addLine(origin, o2, COLORS.YELLOW); // debugging + + // The adjacent side of a right triangle containg the + // solution as one of the points + var v3 = Vec3.multiply(0.5, v2); + + // The length of the adjacent side of the triangle + var l1 = Vec3.length(v3); + // The length of the hypotenuse + var r = BALL_DISTANCE; + + // No connection possible + if (l1 > r) { + return false; + } + + // The length of the opposite side + var l2 = Math.sqrt(r * r - l1 * l1); + + // vector with the length and direction of the opposite side + var v4 = Vec3.multiply(l2, Vec3.normalize(o2)); + + // Add the adjacent vector and the opposite vector to get the + // hypotenuse vector + var result = Vec3.sum(v3, v4); + // move back into world space + result = Vec3.sum(origin, result); // update the entity - var newPosition = Vec3.sum(vector, fromPos); - Entities.editEntity(to, { position: newPosition }); - } else if (1 == edgeCount) { - // FIXME - // find the other end of the edge already connected, call it ball2 - // given two spheres of radius BALL_DISTANCE centered at fromPos and ball2.position, - // find the closest point of intersection to toPos - // move the ball to toPos - } else if (2 == edgeCount) { - // FIXME + Entities.editEntity(to, { position: result }); } else { - // FIXME check for the ability to move fromPos - return false; + // Has a bug of some kind... validation fails after this + + // Debugging marker + //Entities.addEntity(mergeObjects(BALL_PROTOTYPE, { + // position: origin, + // color: COLORS.YELLOW, + // dimensions: Vec3.multiply(0.4, BALL_DIMENSIONS) + //})); + + var v1 = Vec3.subtract(fromPos, origin); + // addLine(origin, v1, COLORS.RED); // debugging + + + var v2 = Vec3.subtract(toPos, origin); + // addLine(origin, v2, COLORS.GREEN); // debugging + + // the lengths of v1 and v2 represent the lengths of two sides + // of the triangle we need to build. + var l1 = Vec3.length(v1); + var l2 = Vec3.length(v2); + // The remaining side is the edge we are trying to create, so + // it will be of length BALL_DISTANCE + var l3 = BALL_DISTANCE; + // given this triangle, we want to know the angle between l1 and l2 + // (this is NOT the same as the angle between v1 and v2, because we + // are trying to rotate v2 around o1 to find a solution + // Use law of cosines to find the angle: cos A = (b^2 + c^2 − a^2) / + // 2bc + var cosA = (l1 * l1 + l2 * l2 - l3 * l3) / (2.0 * l1 * l2); + + // Having this angle gives us all three angles of the right triangle + // containing + // the solution, along with the length of the hypotenuse, which is + // l2 + var hyp = l2; + // We need to find the length of the adjacent and opposite sides + // since cos(A) = adjacent / hypotenuse, then adjacent = hypotenuse + // * cos(A) + var adj = hyp * cosA; + // Pythagoras gives us the opposite side length + var opp = Math.sqrt(hyp * hyp - adj * adj); + + // v1 is the direction vector we need for the adjacent side, so + // resize it to + // the proper length + v1 = Vec3.multiply(adj, Vec3.normalize(v1)); + // addLine(origin, v1, COLORS.GREEN); // debugging + + // FIXME, these are not the right normals, because the ball needs to rotate around the origin + + // This is the normal to the plane on which our solution lies + var o1 = Vec3.cross(v1, v2); + + // Our final side is a normal to the plane defined by o1 and v1 + // and is of length opp + var o2 = Vec3.multiply(opp, Vec3.normalize(Vec3.cross(o1, v1))); + + // Our final result is the sum of v1 and o2 (opposite side vector + + // adjacent side vector) + var result = Vec3.sum(v1, o2); + // Move back into world space + result = Vec3.sum(origin, result); + // update the entity + Entities.editEntity(to, { position: result }); } } - - // Fixup existing edges - for (var edgeId in this.balls[to]) { + // Fixup existing edges if we moved the ball + for (var edgeId in this.nodes[to]) { this.fixupEdge(edgeId); } - - // FIXME, find the correct orientation for a box or model between the two balls - // for now use a line - var newEdge = Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, this.findEdgeParams(from, to))); - - this.edges[newEdge] = {}; - this.edges[newEdge][from] = true; - this.edges[newEdge][to] = true; - this.balls[from][newEdge] = true; - this.balls[to][newEdge] = true; - this.validate(); + + this.createEdge(from, to); return true; } -MagBalls.prototype.findEdgeParams = function(startBall, endBall) { - var startBallPos = this.getBallPosition(startBall); - var endBallPos = this.getBallPosition(endBall); - var vector = Vec3.subtract(endBallPos, startBallPos); - return { - position: startBallPos, - linePoints: [ ZERO_VECTOR, vector ] - }; +MagBalls.prototype.tryCreateRing = function(fromBalls, to) { + // FIXME, if the user tries to connect two points, attempt to + // walk the graph and see if they're creating a ring of 4 or + // more vertices, if so and they're within N percent of lying + // on a plane, then adjust them all so they lie on a plance + return false; +} + +function pausecomp(millis) { + var date = new Date(); + var curDate = null; + do { curDate = new Date(); } while(curDate-date < millis); +} + +// find a normal between three points +function findNormal(a, b, c) { + var aa = Vec3.subtract(a, b); + var cc = Vec3.subtract(c, b); + return Vec3.cross(aa, cc); +} + +MagBalls.prototype.tryCreateFan = function(fromBalls, to) { + logDebug("Attempting to create fan"); + // if the user tries to connect three points, attempt to + // walk the graph and see if they're creating fan, adjust all the + // points to lie on a plane an equidistant from the shared vertex + + // A fan may exist if given three potential connections, two of the connection + // share and edge with the third + var a = fromBalls[0]; + var b = fromBalls[1]; + var c = fromBalls[2]; + var ab = this.areConnected(a, b); + var bc = this.areConnected(b, c); + var ca = this.areConnected(c, a); + if (ab && bc && ca) { + // tetrahedron, let the generic code handle it + return false; + } + var crux = null; + var left = null; + var right = null; + if (ab && bc) { + crux = b; + left = a; + right = c; + } else if (bc && ca) { + crux = a; + left = b; + right = a; + } else if (ca && ab) { + crux = a; + left = c; + right = b; + } + if (crux == null) { + // we don't have two nodes which share edges with the third, so fail + return false; + } + var loop = this.findShortestPath(left, right, { exclude: crux }); + if (!loop) { + return false; + } + + // find the normal to the target plane + var origin = this.getNodePosition(crux); + var normals = []; + var averageNormal = ZERO_VECTOR; + for (var i = 0; i < loop.length - 2; ++i) { + var a = loop[i]; + var b = loop[i + 1]; + var c = loop[i + 2]; + var apos = this.getNodePosition(a); + var bpos = this.getNodePosition(b); + var cpos = this.getNodePosition(c); + var normal = Vec3.normalize(findNormal(apos, bpos, cpos)); + averageNormal = Vec3.sum(averageNormal, normal); + addLine(bpos, normal, COLORS.YELLOW); + normals.push(normal); + } + averageNormal = Vec3.normalize(Vec3.multiply(1 / normals.length, averageNormal)); + + addLine(origin, averageNormal, COLORS.RED); + + // FIXME need to account for locked nodes... if there are 3 locked nodes on the loop, + // then find their cross product + // if there are more than 3 locked nodes on the loop, check if they have matching cross + // products, otherwise fail + + return false; } MagBalls.prototype.fixupEdge = function(edgeId) { var ballsInEdge = Object.keys(this.edges[edgeId]); - Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1])); + var customProperties = this.getEdgeProperties(ballsInEdge[0], ballsInEdge[1]); + Entities.editEntity(edgeId, customProperties); } -// Given two balls, how big is the difference between their distances and the stick length -MagBalls.prototype.getStickLengthVariance = function(a, b) { - var apos = this.getBallPosition(a); - var bpos = this.getBallPosition(b); - var distance = Vec3.distance(apos, bpos); - var variance = Math.abs(distance - BALL_DISTANCE); - return variance; +MagBalls.prototype.getVariance = function(distance) { + // Given two points, how big is the difference between their distance + // and the desired length length + return (Math.abs(distance - BALL_DISTANCE)) / BALL_DISTANCE; } -// FIXME remove unconnected balls +// remove unconnected balls MagBalls.prototype.clean = function() { - //var deletedBalls = {}; - //if (Object.keys(this.balls).length > 1) { - // for (var ball in this.balls) { - // if (!this.getConnections(ball)) { - // deletedBalls[ball] = true; - // Entities.deleteEntity(ball); - // } - // } - //} - //for (var ball in deletedBalls) { - // delete this.balls[ball]; - //} -} - -MagBalls.prototype.getBallPosition = function(ball) { - var properties = Entities.getEntityProperties(ball); - return properties.position; -} - -MagBalls.prototype.breakEdges = function(ballId) { - for (var edgeId in this.balls[ballId]) { - this.destroyEdge(edgeId); + // do nothing unless there are at least 2 balls and one edge + if (Object.keys(this.nodes).length < 2 || !Object.keys(this.edges).length) { + return; } - // This shouldn't be necessary - this.balls[ballId] = {}; -} - -MagBalls.prototype.destroyEdge = function(edgeId) { - logDebug("Deleting edge " + edgeId); - // Delete the edge from other balls - for (var edgeBallId in this.edges[edgeId]) { - delete this.balls[edgeBallId][edgeId]; + var disconnectedNodes = {}; + for (var nodeId in this.nodes) { + if (!Object.keys(this.nodes[nodeId]).length) { + disconnectedNodes[nodeId] = true; + } + } + for (var nodeId in disconnectedNodes) { + this.destroyNode(nodeId); } - delete this.edges[edgeId]; - Entities.deleteEntity(edgeId); - this.validate(); -} - -MagBalls.prototype.destroyBall = function(ballId) { - logDebug("Deleting ball " + ballId); - breakEdges(ballId); - Entities.deleteEntity(ballId); } +// remove all balls MagBalls.prototype.clear = function() { if (DEBUG_MAGSTICKS) { + this.deleteAll(); var ids = Entities.findEntities(MyAvatar.position, 50); var result = []; ids.forEach(function(id) { @@ -366,89 +551,23 @@ MagBalls.prototype.clear = function() { } } -MagBalls.prototype.areConnected = function(a, b) { - for (var edge in this.balls[a]) { - // edge already exists - if (this.balls[b][edge]) { - return true; - } - } - return false; -} - +// Override to check lengths as well as connection consistency MagBalls.prototype.validate = function() { - var error = false; - for (ballId in this.balls) { - for (edgeId in this.balls[ballId]) { - var edge = this.edges[edgeId]; - if (!edge) { - logError("Error: ball " + ballId + " refers to unknown edge " + edgeId); + var error = Graph.prototype.validate.call(this); + + if (!error) { + for (edgeId in this.edges) { + var length = this.getEdgeLength(edgeId); + var variance = this.getVariance(length); + if (variance > EPSILON) { + Entities.editEntity(edgeId, { color: COLORS.RED }); + + logDebug("Edge " + edgeId + " length " + (length * 100).toFixed(2) + " cm variance " + (variance * 100).toFixed(3) + "%"); error = true; - continue; - } - if (!edge[ballId]) { - logError("Error: ball " + ballId + " refers to edge " + edgeId + " but not vice versa"); - error = true; - continue; } } - } - - for (edgeId in this.edges) { - for (ballId in this.edges[edgeId]) { - var ball = this.balls[ballId]; - if (!ball) { - logError("Error: edge " + edgeId + " refers to unknown ball " + ballId); - error = true; - continue; - } - if (!ball[edgeId]) { - logError("Error: edge " + edgeId + " refers to ball " + ballId + " but not vice versa"); - error = true; - continue; - } + if (error) { + logDebug(EPSILON); } } - if (error) { - logDebug(JSON.stringify({ edges: this.edges, balls: this.balls }, null, 2)); - } } - -// FIXME fetch from a subkey of user data to support non-destructive modifications -MagBalls.prototype.setUserData = function(id, data) { - Entities.editEntity(id, { userData: JSON.stringify(data) }); -} - -// FIXME do non-destructive modification of the existing user data -MagBalls.prototype.getUserData = function(id) { - var results = null; - var properties = Entities.getEntityProperties(id); - if (properties.userData) { - results = JSON.parse(this.properties.userData); - } - return results; -} - -//MagBalls.prototype.findBalls = function() { -// var ids = Entities.findEntities(MyAvatar.position, 50); -// var result = []; -// ids.forEach(function(id) { -// var properties = Entities.getEntityProperties(id); -// if (properties.name == BALL_NAME) { -// result.push(id); -// } -// }, this); -// return result; -//}; -// -//MagBalls.prototype.findEdges = function() { -// var ids = Entities.findEntities(MyAvatar.position, 50); -// var result = []; -// ids.forEach(function(id) { -// var properties = Entities.getEntityProperties(id); -// if (properties.name == EDGE_NAME) { -// result.push(id); -// } -// }, this); -// return result; -//}; diff --git a/examples/toys/magSticks/springEdgeEntity.js b/examples/toys/magSticks/springEdgeEntity.js new file mode 100644 index 0000000000..9e0ebd2115 --- /dev/null +++ b/examples/toys/magSticks/springEdgeEntity.js @@ -0,0 +1,143 @@ +// +// Created by Bradley Austin Davis on 2015/08/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +(function(){ + this.preload = function(entityId) { + this.MIN_CHECK_INTERVAL = 0.05; + this.MAX_VARIANCE = 0.005; + this.ZERO_VECTOR = { x: 0, y: 0, z: 0 }; + + this.entityId = entityId; + var properties = Entities.getEntityProperties(this.entityId); + var userData = JSON.parse(properties.userData); + this.start = userData.magBalls.start; + this.end = userData.magBalls.end; + this.originalColor = properties.color; + this.desiredLength = userData.magBalls.length; + this.timeSinceLastUpdate = 0; + this.nextCheckInterval = this.MIN_CHECK_INTERVAL; + + print("preload("+entityId+") " + this.start + " -> " + this.end + " " + this.desiredLength); + + var _this = this; + this.updateWrapper = function(deltaTime) { + _this.onUpdate(deltaTime); + }; + Script.update.connect(this.updateWrapper); + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); + + Entities.deletingEntity.connect(function(entityId) { + if (_this.entityId == entityId) { + _this.onCleanup(); + } + }); + }; + + this.onUpdate = function(deltaTime) { + this.timeSinceLastUpdate += deltaTime; + if (this.timeSinceLastUpdate > this.nextCheckInterval) { + this.updateProperties(); + this.timeSinceLastUpdate = 0; + var length = this.getLength(); + if (length == 0) { + this.onCleanup(); + return; + } + var variance = this.getVariance(length); + if (Math.abs(variance) <= this.MAX_VARIANCE) { + this.incrementCheckInterval(); + return; + } + this.decrementCheckInterval(); + print("Length is wrong: " + (length * 100).toFixed(1) + "cm variance " + variance); + + var adjustmentVector = Vec3.multiply(variance / 4, this.vector); + var newPosition = Vec3.sum(Vec3.multiply(-1, adjustmentVector), this.position); + var newVector = Vec3.sum(Vec3.multiply(2, adjustmentVector), this.vector); + var newLength = Vec3.length(newVector); + var newVariance = this.getVariance(newLength); + var color = { color: this.originalColor } + if (Math.abs(newVariance) > this.MAX_VARIANCE) { + color = { red: 255, green: 0, blue: 0 }; + } + print("Updating entity to new variance " + newVariance); + Entities.editEntity(this.entityId, { + color: color, + position: newPosition, + linePoints: [ this.ZERO_VECTOR, newVector ] + }); + Entities.editEntity(this.start, { + position: newPosition + }); + Entities.editEntity(this.end, { + position: Vec3.sum(newPosition, newVector) + }); + + } + } + + this.incrementCheckInterval = function() { + this.nextCheckInterval = Math.min(this.nextCheckInterval * 2.0, 1.0); + } + + this.decrementCheckInterval = function() { + this.nextCheckInterval = 0.05; + } + + this.onCleanup = function() { + print("Stopping spring script"); + Script.update.disconnect(this.updateWrapper); + } + + this.getVariance = function(length) { + if (!length) { + length = this.getLength(); + } + var difference = this.desiredLength - length; + return difference / this.desiredLength; + } + + this.getLength = function() { + return Vec3.length(this.vector); + } + + this.getPosition = function(entityId) { + var properties = Entities.getEntityProperties(entityId); + return properties.position; + } + + this.updateProperties = function() { + var properties = Entities.getEntityProperties(this.entityId); + var curStart = properties.position; + var curVector = properties.linePoints[1] + var curEnd = Vec3.sum(curVector, curStart); + var startPos = this.getPosition(this.start); + var endPos = this.getPosition(this.end); + var startError = Vec3.distance(curStart, startPos); + var endError = Vec3.distance(curEnd, endPos); + this.vector = Vec3.subtract(endPos, startPos); + if (startError > 0.005 || endError > 0.005) { + Entities.editEntity(this.entityId, { + position: startPos, + linePoints: [ this.ZERO_VECTOR, this.vector ] + }); + } + this.position = startPos; + } + + this.enterEntity = function(entityId) { + print("enterEntity("+entityId+")"); + }; + + this.leaveEntity = function(entityId) { + print("leaveEntity("+entityId+")"); + }; +}); diff --git a/examples/toys/magSticks/utils.js b/examples/toys/magSticks/utils.js index 43e4f800fb..5eaf1791fb 100644 --- a/examples/toys/magSticks/utils.js +++ b/examples/toys/magSticks/utils.js @@ -1,17 +1,7 @@ -DEBUG_MAGSTICKS = true; - -debugPrint = function (str) { - if (DEBUG_MAGSTICKS) { - print(str); - } -} - -vec3toStr = function (v) { - return "{ " + - (Math.round(v.x*1000)/1000) + ", " + - (Math.round(v.y*1000)/1000) + ", " + - (Math.round(v.z*1000)/1000) + " }"; +vec3toStr = function (v, digits) { + if (!digits) { digits = 3; } + return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }"; } scaleLine = function (start, end, scale) { @@ -31,6 +21,79 @@ findAction = function(name) { return 0; } + +var LINE_DIMENSIONS = { + x: 5, + y: 5, + z: 5 +} + +var EDGE_NAME = "MagStick" + +var LINE_PROTOTYPE = { + type: "Line", + name: EDGE_NAME, + color: COLORS.CYAN, + dimensions: LINE_DIMENSIONS, + lineWidth: 5, + visible: true, + ignoreCollisions: true, + collisionsWillMove: false +} + + +addLine = function(origin, vector, color) { + if (!color) { + color = COLORS.WHITE + } + return Entities.addEntity(mergeObjects(LINE_PROTOTYPE, { + position: origin, + linePoints: [ + ZERO_VECTOR, + vector, + ], + color: color + })); +} + +// FIXME fetch from a subkey of user data to support non-destructive modifications +setEntityUserData = function(id, data) { + Entities.editEntity(id, { userData: JSON.stringify(data) }); +} + +// FIXME do non-destructive modification of the existing user data +getEntityUserData = function(id) { + var results = null; + var properties = Entities.getEntityProperties(id); + if (properties.userData) { + results = JSON.parse(this.properties.userData); + } + return results; +} + +// Non-destructively modify the user data of an entity. +setEntityCustomData = function(customKey, id, data) { + var userData = getEntityUserData(id); + userData[customKey] = data; + setEntityUserData(id, userData); +} + +getEntityCustomData = function(customKey, id, defaultValue) { + var userData = getEntityUserData(); + return userData[customKey] ? userData[customKey] : defaultValue; +} + +mergeObjects = function(proto, custom) { + var result = {}; + for (var attrname in proto) { + result[attrname] = proto[attrname]; + } + for (var attrname in custom) { + result[attrname] = custom[attrname]; + } + return result; +} + logWarn = function(str) { print(str); } @@ -44,5 +107,5 @@ logInfo = function(str) { } logDebug = function(str) { - debugPrint(str); + print(str); } \ No newline at end of file From cffb0be38487e0a38f83dd5a8081eef0d0efd011 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 29 Aug 2015 22:49:14 -0700 Subject: [PATCH 26/29] Removing edge finding calculations --- examples/toys/magSticks.js | 2 +- examples/toys/magSticks/graph.js | 7 + examples/toys/magSticks/handController.js | 32 +- examples/toys/magSticks/highlighter.js | 7 + examples/toys/magSticks/magBalls.js | 376 +------------------- examples/toys/magSticks/springEdgeEntity.js | 34 +- examples/toys/magSticks/utils.js | 7 + 7 files changed, 47 insertions(+), 418 deletions(-) diff --git a/examples/toys/magSticks.js b/examples/toys/magSticks.js index 9461ffd047..1c51d1d0da 100644 --- a/examples/toys/magSticks.js +++ b/examples/toys/magSticks.js @@ -16,7 +16,7 @@ Script.include("magSticks/handController.js"); var magBalls = new MagBalls(); // Clear any previous balls -magBalls.clear(); +// magBalls.clear(); // How close do we need to be to a ball to select it.... radius + 10% var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.5; diff --git a/examples/toys/magSticks/graph.js b/examples/toys/magSticks/graph.js index 8a5c00451d..8ea9f97fc3 100644 --- a/examples/toys/magSticks/graph.js +++ b/examples/toys/magSticks/graph.js @@ -1,3 +1,10 @@ +// +// Created by Bradley Austin Davis on 2015/08/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// // A collection of nodes and edges connecting them. Graph = function() { diff --git a/examples/toys/magSticks/handController.js b/examples/toys/magSticks/handController.js index cf3ef96b4a..57bef9be4a 100644 --- a/examples/toys/magSticks/handController.js +++ b/examples/toys/magSticks/handController.js @@ -1,28 +1,14 @@ - +// +// Created by Bradley Austin Davis on 2015/08/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// LEFT_CONTROLLER = 0; RIGHT_CONTROLLER = 1; -WAND_LIFETIME = 30; - -var WAND_PROPERTIES = { - type: "Model", - modelURL: "file:///f:/Downloads/wand.FBX", - ignoreForCollisions: true, - dimensions: { - x: 0.1, - y: 0.1, - z: 0.1 - }, - lifetime: 30 -}; - -if (!Date.now) { - Date.now = function now() { - return new Date().getTime(); - }; -} - - +// FIXME add a customizable wand model and a mechanism to switch between wands HandController = function(side) { this.side = side; this.palm = 2 * side; @@ -38,9 +24,7 @@ HandController = function(side) { solid: true, visible: false, }); - //this.wand = Entities.addEntity(WAND_PROPERTIES); - // Connect to desired events var _this = this; Controller.actionEvent.connect(function(action, state) { diff --git a/examples/toys/magSticks/highlighter.js b/examples/toys/magSticks/highlighter.js index 7316549c84..149d9ec5b7 100644 --- a/examples/toys/magSticks/highlighter.js +++ b/examples/toys/magSticks/highlighter.js @@ -1,3 +1,10 @@ +// +// Created by Bradley Austin Davis on 2015/08/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var SELECTION_OVERLAY = { position: { x: 0, diff --git a/examples/toys/magSticks/magBalls.js b/examples/toys/magSticks/magBalls.js index 9a0465c582..ca843fc2ef 100644 --- a/examples/toys/magSticks/magBalls.js +++ b/examples/toys/magSticks/magBalls.js @@ -1,3 +1,10 @@ +// +// Created by Bradley Austin Davis on 2015/08/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var BALL_NAME = "MagBall" var EDGE_NAME = "MagStick" @@ -70,14 +77,7 @@ var EDGE_PROTOTYPE = LINE_PROTOTYPE; // A collection of balls and edges connecting them. MagBalls = function() { this.selectedNodes = {}; - Graph.call(this); - - var _this = this; - Script.update.connect(function(deltaTime) { - _this.onUpdate(deltaTime); - }); - Script.scriptEnding.connect(function() { _this.onCleanup(); }); @@ -94,10 +94,10 @@ MagBalls.prototype.createNodeEntity = function(customProperies) { return Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies)); } -MagBalls.prototype.getEdgeProperties = function(nodeIdA, nodeIdB) { +MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) { var apos = this.getNodePosition(nodeIdA); var bpos = this.getNodePosition(nodeIdB); - return { + return Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, { position: apos, linePoints: [ ZERO_VECTOR, Vec3.subtract(bpos, apos) ], userData: JSON.stringify({ @@ -107,12 +107,7 @@ MagBalls.prototype.getEdgeProperties = function(nodeIdA, nodeIdB) { length: BALL_DISTANCE } }) - }; -} - -MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) { - var customProperties = this.getEdgeProperties(nodeIdA, nodeIdB) - return Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, customProperties)); + })); } MagBalls.prototype.findPotentialEdges = function(nodeId) { @@ -178,340 +173,10 @@ MagBalls.prototype.releaseBall = function(releasedBall) { for (var otherBallId in targets) { this.createEdge(otherBallId, releasedBall); } - this.clean(); - return; - - // sort other balls by distance from the stick length - var createdEdge = false; - while (true) { - // Get the list of candidate connections - var variances = this.findPotentialEdges(releasedBall); - // sort them by the difference from an ideal distance - var targetBalls = Object.keys(variances); - if (!targetBalls.length) { - break; - } - - // special case when there are 2 potential matches, - // try to create a ring on a plane - if (targetBalls.length == 2 && this.tryCreateRing(targetBalls, releasedBall)) { - createdEdge = true; - break; - } - - // special case when there are 3 potential matches, - // try create a fan - if (targetBalls.length == 3 && this.tryCreateFan(targetBalls, releasedBall)) { - createdEdge = true; - break; - } - - targetBalls.sort(function(a, b){ - return variances[a] - variances[b]; - }); - - // find the nearest matching unconnected ball and create an edge to it - // if possible - // note that createEdge will preferentially move the second entity, the - // released ball - // in order to create a fit - var str = "Attempt to create edge between " + targetBalls[0] + " and " + releasedBall; - if (!this.tryCreateEdge(targetBalls[0], releasedBall)) { - logDebug(str + "failed"); - var nodeDistance = this.getNodeDistance(targetBalls[0], releasedBall); - var variance = this.getVariance(nodeDistance); - logDebug("Distance was " + (nodeDistance * 100).toFixed(2) + "cm with a variance of " + variance); - break; - } - logDebug(str + " succeeded"); - - releasePosition = this.getNodePosition(releasedBall); - - // Record that we created at least one edge - createdEdge = true; - } - - if (createdEdge) { - // FIXME play a snap sound - } - this.clean(); this.validate(); } -MagBalls.prototype.tryCreateEdge = function(from, to) { - var fromPos = this.getNodePosition(from); - var toPos = this.getNodePosition(to); - var vector = Vec3.subtract(toPos, fromPos); - var originalLength = Vec3.length(vector); - - var variance = this.getVariance(originalLength); - // if they're already at a close enough distance, just create the edge - if (variance < EPSILON) { - logDebug("Length " + originalLength + " with variance of " + (variance * 100).toFixed(2) + " is within epislon " + EPSILON) ; - // close enough for government work - this.createEdge(from, to); - return true; - } - - // FIXME find the constraints on `from` and `to` and determine if there is a - // new positiong - // for 'to' that keeps it's current connections and connects with 'from' - // Do only first order scanning for now, unless we can expose a mechanism - // for interacting - // to reach equilibrium via Bullet - // * a ball zero edges is fully free... - // * a ball with one edge free to move on a sphere section - // * a ball with two edges is free to move in a circle - // * a ball with more than two edges is not free to move - - // Zero edges for the destination - var existingEdges = Object.keys(this.nodes[to]); - var edgeCount = existingEdges.length; - if (!edgeCount) { - // Easy case 1: unconnected ball - // Move the ball along it's current path to match the desired distance - vector = Vec3.multiply(BALL_DISTANCE, Vec3.normalize(vector)); - // Add the vector to the starting position to find the new position - var newPosition = Vec3.sum(vector, fromPos); - // update the entity - Entities.editEntity(to, { position: newPosition }); - moved = true; - } else if (edgeCount > 2) { - // Easy case 2: locked position ball - // FIXME should check the target ball to see if it can be moved. - // Possible easy solution is to recurse into this.createEdge and swap - // the parameters, - // but need to prevert infinite recursion - // for now... - return false; - } else { - var connectedBalls = this.getConnectedNodes(to); - // find the other balls connected, will be either 1 or 2 - var origin = { x: 0, y: 0, z: 0 }; - for (var nodeId in connectedBalls) { - origin = Vec3.sum(origin, this.getNodePosition(nodeId)); - } - - if (edgeCount > 1) { - origin = Vec3.multiply(origin, 1 / edgeCount); - } - // logDebug("Using origin " + vec3toStr(origin)); - - if (edgeCount == 1) { - // vectors from the temp origin to the two balls. - var v1 = Vec3.subtract(toPos, origin); - var v2 = Vec3.subtract(fromPos, origin); - - // ortogonal to the solution plane - var o1 = Vec3.normalize(Vec3.cross(Vec3.normalize(v2), Vec3.normalize(v1))); - // addLine(origin, o1, COLORS.RED); // debugging - - // orthogonal to o1, lying on the solution plane - var o2 = Vec3.normalize(Vec3.cross(o1, Vec3.normalize(v2))); - // addLine(origin, o2, COLORS.YELLOW); // debugging - - // The adjacent side of a right triangle containg the - // solution as one of the points - var v3 = Vec3.multiply(0.5, v2); - - // The length of the adjacent side of the triangle - var l1 = Vec3.length(v3); - // The length of the hypotenuse - var r = BALL_DISTANCE; - - // No connection possible - if (l1 > r) { - return false; - } - - // The length of the opposite side - var l2 = Math.sqrt(r * r - l1 * l1); - - // vector with the length and direction of the opposite side - var v4 = Vec3.multiply(l2, Vec3.normalize(o2)); - - // Add the adjacent vector and the opposite vector to get the - // hypotenuse vector - var result = Vec3.sum(v3, v4); - // move back into world space - result = Vec3.sum(origin, result); - // update the entity - Entities.editEntity(to, { position: result }); - } else { - // Has a bug of some kind... validation fails after this - - // Debugging marker - //Entities.addEntity(mergeObjects(BALL_PROTOTYPE, { - // position: origin, - // color: COLORS.YELLOW, - // dimensions: Vec3.multiply(0.4, BALL_DIMENSIONS) - //})); - - var v1 = Vec3.subtract(fromPos, origin); - // addLine(origin, v1, COLORS.RED); // debugging - - - var v2 = Vec3.subtract(toPos, origin); - // addLine(origin, v2, COLORS.GREEN); // debugging - - // the lengths of v1 and v2 represent the lengths of two sides - // of the triangle we need to build. - var l1 = Vec3.length(v1); - var l2 = Vec3.length(v2); - // The remaining side is the edge we are trying to create, so - // it will be of length BALL_DISTANCE - var l3 = BALL_DISTANCE; - // given this triangle, we want to know the angle between l1 and l2 - // (this is NOT the same as the angle between v1 and v2, because we - // are trying to rotate v2 around o1 to find a solution - // Use law of cosines to find the angle: cos A = (b^2 + c^2 − a^2) / - // 2bc - var cosA = (l1 * l1 + l2 * l2 - l3 * l3) / (2.0 * l1 * l2); - - // Having this angle gives us all three angles of the right triangle - // containing - // the solution, along with the length of the hypotenuse, which is - // l2 - var hyp = l2; - // We need to find the length of the adjacent and opposite sides - // since cos(A) = adjacent / hypotenuse, then adjacent = hypotenuse - // * cos(A) - var adj = hyp * cosA; - // Pythagoras gives us the opposite side length - var opp = Math.sqrt(hyp * hyp - adj * adj); - - // v1 is the direction vector we need for the adjacent side, so - // resize it to - // the proper length - v1 = Vec3.multiply(adj, Vec3.normalize(v1)); - // addLine(origin, v1, COLORS.GREEN); // debugging - - // FIXME, these are not the right normals, because the ball needs to rotate around the origin - - // This is the normal to the plane on which our solution lies - var o1 = Vec3.cross(v1, v2); - - // Our final side is a normal to the plane defined by o1 and v1 - // and is of length opp - var o2 = Vec3.multiply(opp, Vec3.normalize(Vec3.cross(o1, v1))); - - // Our final result is the sum of v1 and o2 (opposite side vector + - // adjacent side vector) - var result = Vec3.sum(v1, o2); - // Move back into world space - result = Vec3.sum(origin, result); - // update the entity - Entities.editEntity(to, { position: result }); - } - } - - // Fixup existing edges if we moved the ball - for (var edgeId in this.nodes[to]) { - this.fixupEdge(edgeId); - } - - this.createEdge(from, to); - return true; -} - -MagBalls.prototype.tryCreateRing = function(fromBalls, to) { - // FIXME, if the user tries to connect two points, attempt to - // walk the graph and see if they're creating a ring of 4 or - // more vertices, if so and they're within N percent of lying - // on a plane, then adjust them all so they lie on a plance - return false; -} - -function pausecomp(millis) { - var date = new Date(); - var curDate = null; - do { curDate = new Date(); } while(curDate-date < millis); -} - -// find a normal between three points -function findNormal(a, b, c) { - var aa = Vec3.subtract(a, b); - var cc = Vec3.subtract(c, b); - return Vec3.cross(aa, cc); -} - -MagBalls.prototype.tryCreateFan = function(fromBalls, to) { - logDebug("Attempting to create fan"); - // if the user tries to connect three points, attempt to - // walk the graph and see if they're creating fan, adjust all the - // points to lie on a plane an equidistant from the shared vertex - - // A fan may exist if given three potential connections, two of the connection - // share and edge with the third - var a = fromBalls[0]; - var b = fromBalls[1]; - var c = fromBalls[2]; - var ab = this.areConnected(a, b); - var bc = this.areConnected(b, c); - var ca = this.areConnected(c, a); - if (ab && bc && ca) { - // tetrahedron, let the generic code handle it - return false; - } - var crux = null; - var left = null; - var right = null; - if (ab && bc) { - crux = b; - left = a; - right = c; - } else if (bc && ca) { - crux = a; - left = b; - right = a; - } else if (ca && ab) { - crux = a; - left = c; - right = b; - } - if (crux == null) { - // we don't have two nodes which share edges with the third, so fail - return false; - } - var loop = this.findShortestPath(left, right, { exclude: crux }); - if (!loop) { - return false; - } - - // find the normal to the target plane - var origin = this.getNodePosition(crux); - var normals = []; - var averageNormal = ZERO_VECTOR; - for (var i = 0; i < loop.length - 2; ++i) { - var a = loop[i]; - var b = loop[i + 1]; - var c = loop[i + 2]; - var apos = this.getNodePosition(a); - var bpos = this.getNodePosition(b); - var cpos = this.getNodePosition(c); - var normal = Vec3.normalize(findNormal(apos, bpos, cpos)); - averageNormal = Vec3.sum(averageNormal, normal); - addLine(bpos, normal, COLORS.YELLOW); - normals.push(normal); - } - averageNormal = Vec3.normalize(Vec3.multiply(1 / normals.length, averageNormal)); - - addLine(origin, averageNormal, COLORS.RED); - - // FIXME need to account for locked nodes... if there are 3 locked nodes on the loop, - // then find their cross product - // if there are more than 3 locked nodes on the loop, check if they have matching cross - // products, otherwise fail - - return false; -} - -MagBalls.prototype.fixupEdge = function(edgeId) { - var ballsInEdge = Object.keys(this.edges[edgeId]); - var customProperties = this.getEdgeProperties(ballsInEdge[0], ballsInEdge[1]); - Entities.editEntity(edgeId, customProperties); -} MagBalls.prototype.getVariance = function(distance) { // Given two points, how big is the difference between their distance @@ -550,24 +215,3 @@ MagBalls.prototype.clear = function() { }, this); } } - -// Override to check lengths as well as connection consistency -MagBalls.prototype.validate = function() { - var error = Graph.prototype.validate.call(this); - - if (!error) { - for (edgeId in this.edges) { - var length = this.getEdgeLength(edgeId); - var variance = this.getVariance(length); - if (variance > EPSILON) { - Entities.editEntity(edgeId, { color: COLORS.RED }); - - logDebug("Edge " + edgeId + " length " + (length * 100).toFixed(2) + " cm variance " + (variance * 100).toFixed(3) + "%"); - error = true; - } - } - if (error) { - logDebug(EPSILON); - } - } -} diff --git a/examples/toys/magSticks/springEdgeEntity.js b/examples/toys/magSticks/springEdgeEntity.js index 9e0ebd2115..56ef55e5d2 100644 --- a/examples/toys/magSticks/springEdgeEntity.js +++ b/examples/toys/magSticks/springEdgeEntity.js @@ -6,7 +6,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - (function(){ this.preload = function(entityId) { this.MIN_CHECK_INTERVAL = 0.05; @@ -18,26 +17,22 @@ var userData = JSON.parse(properties.userData); this.start = userData.magBalls.start; this.end = userData.magBalls.end; - this.originalColor = properties.color; this.desiredLength = userData.magBalls.length; this.timeSinceLastUpdate = 0; this.nextCheckInterval = this.MIN_CHECK_INTERVAL; - print("preload("+entityId+") " + this.start + " -> " + this.end + " " + this.desiredLength); - + // FIXME do I really need to do this nonsense? var _this = this; this.updateWrapper = function(deltaTime) { _this.onUpdate(deltaTime); }; Script.update.connect(this.updateWrapper); + Script.scriptEnding.connect(function() { _this.onCleanup(); }); - Entities.deletingEntity.connect(function(entityId) { - if (_this.entityId == entityId) { - _this.onCleanup(); - } + _this.onCleanup(); }); }; @@ -57,20 +52,12 @@ return; } this.decrementCheckInterval(); - print("Length is wrong: " + (length * 100).toFixed(1) + "cm variance " + variance); - var adjustmentVector = Vec3.multiply(variance / 4, this.vector); var newPosition = Vec3.sum(Vec3.multiply(-1, adjustmentVector), this.position); var newVector = Vec3.sum(Vec3.multiply(2, adjustmentVector), this.vector); var newLength = Vec3.length(newVector); var newVariance = this.getVariance(newLength); - var color = { color: this.originalColor } - if (Math.abs(newVariance) > this.MAX_VARIANCE) { - color = { red: 255, green: 0, blue: 0 }; - } - print("Updating entity to new variance " + newVariance); Entities.editEntity(this.entityId, { - color: color, position: newPosition, linePoints: [ this.ZERO_VECTOR, newVector ] }); @@ -80,7 +67,6 @@ Entities.editEntity(this.end, { position: Vec3.sum(newPosition, newVector) }); - } } @@ -93,8 +79,10 @@ } this.onCleanup = function() { - print("Stopping spring script"); - Script.update.disconnect(this.updateWrapper); + if (this.updateWrapper) { + Script.update.disconnect(this.updateWrapper); + delete this.updateWrapper; + } } this.getVariance = function(length) { @@ -132,12 +120,4 @@ } this.position = startPos; } - - this.enterEntity = function(entityId) { - print("enterEntity("+entityId+")"); - }; - - this.leaveEntity = function(entityId) { - print("leaveEntity("+entityId+")"); - }; }); diff --git a/examples/toys/magSticks/utils.js b/examples/toys/magSticks/utils.js index 5eaf1791fb..8a522ac41f 100644 --- a/examples/toys/magSticks/utils.js +++ b/examples/toys/magSticks/utils.js @@ -1,3 +1,10 @@ +// +// Created by Bradley Austin Davis on 2015/08/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// vec3toStr = function (v, digits) { if (!digits) { digits = 3; } From a574251173e78477043adb73f224d7faf584102f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 29 Aug 2015 23:06:01 -0700 Subject: [PATCH 27/29] Cleaning up and moving entity script --- .../magBallEdge.js} | 1 + examples/toys/{magSticks => }/magBalls.js | 93 ++++++++++++++++++- examples/toys/magSticks.js | 93 ------------------- 3 files changed, 92 insertions(+), 95 deletions(-) rename examples/{toys/magSticks/springEdgeEntity.js => entityScripts/magBallEdge.js} (99%) rename examples/toys/{magSticks => }/magBalls.js (63%) delete mode 100644 examples/toys/magSticks.js diff --git a/examples/toys/magSticks/springEdgeEntity.js b/examples/entityScripts/magBallEdge.js similarity index 99% rename from examples/toys/magSticks/springEdgeEntity.js rename to examples/entityScripts/magBallEdge.js index 56ef55e5d2..5411a82088 100644 --- a/examples/toys/magSticks/springEdgeEntity.js +++ b/examples/entityScripts/magBallEdge.js @@ -113,6 +113,7 @@ var endError = Vec3.distance(curEnd, endPos); this.vector = Vec3.subtract(endPos, startPos); if (startError > 0.005 || endError > 0.005) { + print("Fixing up edge"); Entities.editEntity(this.entityId, { position: startPos, linePoints: [ this.ZERO_VECTOR, this.vector ] diff --git a/examples/toys/magSticks/magBalls.js b/examples/toys/magBalls.js similarity index 63% rename from examples/toys/magSticks/magBalls.js rename to examples/toys/magBalls.js index ca843fc2ef..46d4905d0e 100644 --- a/examples/toys/magSticks/magBalls.js +++ b/examples/toys/magBalls.js @@ -1,13 +1,20 @@ // -// Created by Bradley Austin Davis on 2015/08/29 +// Created by Bradley Austin Davis on 2015/08/25 // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("magSticks/constants.js"); +Script.include("magSticks/utils.js"); +Script.include("magSticks/graph.js"); +Script.include("magSticks/highlighter.js"); +Script.include("magSticks/handController.js"); + var BALL_NAME = "MagBall" var EDGE_NAME = "MagStick" +var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.5; var BALL_DIMENSIONS = { x: BALL_SIZE, @@ -47,6 +54,10 @@ var LINE_DIMENSIONS = { z: 5 } +//var EDGE_ENTITY_SCRIPT_BASE = "file:/Users/bdavis/Git/hifi/examples/entityScripts"; +var EDGE_ENTITY_SCRIPT_BASE = "https://s3.amazonaws.com/hifi-public/scripts/entityScripts"; +var EDGE_ENTITY_SCRIPT = EDGE_ENTITY_SCRIPT_BASE + "/magBallEdge.js", + var LINE_PROTOTYPE = { type: "Line", name: EDGE_NAME, @@ -56,7 +67,7 @@ var LINE_PROTOTYPE = { visible: true, ignoreCollisions: true, collisionsWillMove: false, - script: "file:/Users/bdavis/Git/hifi/examples/toys/magSticks/springEdgeEntity.js" + script: EDGE_ENTITY_SCRIPT } var EDGE_PROTOTYPE = LINE_PROTOTYPE; @@ -215,3 +226,81 @@ MagBalls.prototype.clear = function() { }, this); } } + +var magBalls = new MagBalls(); + +// Clear any previous balls +// magBalls.clear(); + +// How close do we need to be to a ball to select it.... radius + 10% + +BallController = function(side) { + HandController.call(this, side); + this.highlighter = new Highlighter(); + this.highlighter.setSize(BALL_SIZE); + this.ghostEdges = {}; +} + +BallController.prototype = Object.create( HandController.prototype ); + +BallController.prototype.onUpdate = function(deltaTime) { + HandController.prototype.onUpdate.call(this, deltaTime); + if (!this.selected) { + // Find the highlight target and set it. + var target = magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(target); + return; + } + this.highlighter.highlight(null); + Entities.editEntity(this.selected, { position: this.tipPosition }); + var targetBalls = magBalls.findPotentialEdges(this.selected); + for (var ballId in targetBalls) { + if (!this.ghostEdges[ballId]) { + // create the ovleray + this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { + start: magBalls.getNodePosition(ballId), + end: this.tipPosition, + color: { red: 255, green: 0, blue: 0}, + alpha: 1, + lineWidth: 5, + visible: true, + }); + } else { + Overlays.editOverlay(this.ghostEdges[ballId], { + end: this.tipPosition, + }); + } + } + for (var ballId in this.ghostEdges) { + if (!targetBalls[ballId]) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } + } +} + +BallController.prototype.onClick = function() { + this.selected = magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(null); +} + +BallController.prototype.onRelease = function() { + this.clearGhostEdges(); + magBalls.releaseBall(this.selected); + this.selected = null; +} + +BallController.prototype.clearGhostEdges = function() { + for(var ballId in this.ghostEdges) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } +} + +BallController.prototype.onCleanup = function() { + HandController.prototype.onCleanup.call(this); + this.clearGhostEdges(); +} + +// FIXME resolve some of the issues with dual controllers before allowing both controllers active +var handControllers = [new BallController(LEFT_CONTROLLER)]; //, new HandController(RIGHT) ]; diff --git a/examples/toys/magSticks.js b/examples/toys/magSticks.js deleted file mode 100644 index 1c51d1d0da..0000000000 --- a/examples/toys/magSticks.js +++ /dev/null @@ -1,93 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/08/25 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -Script.include("magSticks/constants.js"); -Script.include("magSticks/utils.js"); -Script.include("magSticks/graph.js"); -Script.include("magSticks/magBalls.js"); -Script.include("magSticks/highlighter.js"); -Script.include("magSticks/handController.js"); - -var magBalls = new MagBalls(); - -// Clear any previous balls -// magBalls.clear(); - -// How close do we need to be to a ball to select it.... radius + 10% -var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.5; - -BallController = function(side) { - HandController.call(this, side); - this.highlighter = new Highlighter(); - this.highlighter.setSize(BALL_SIZE); - this.ghostEdges = {}; -} - -BallController.prototype = Object.create( HandController.prototype ); - -BallController.prototype.onUpdate = function(deltaTime) { - HandController.prototype.onUpdate.call(this, deltaTime); - if (!this.selected) { - // Find the highlight target and set it. - var target = magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(target); - return; - } - this.highlighter.highlight(null); - Entities.editEntity(this.selected, { position: this.tipPosition }); - var targetBalls = magBalls.findPotentialEdges(this.selected); - for (var ballId in targetBalls) { - if (!this.ghostEdges[ballId]) { - // create the ovleray - this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { - start: magBalls.getNodePosition(ballId), - end: this.tipPosition, - color: { red: 255, green: 0, blue: 0}, - alpha: 1, - lineWidth: 5, - visible: true, - }); - } else { - Overlays.editOverlay(this.ghostEdges[ballId], { - end: this.tipPosition, - }); - } - } - for (var ballId in this.ghostEdges) { - if (!targetBalls[ballId]) { - Overlays.deleteOverlay(this.ghostEdges[ballId]); - delete this.ghostEdges[ballId]; - } - } -} - -BallController.prototype.onClick = function() { - this.selected = magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(null); -} - -BallController.prototype.onRelease = function() { - this.clearGhostEdges(); - magBalls.releaseBall(this.selected); - this.selected = null; -} - -BallController.prototype.clearGhostEdges = function() { - for(var ballId in this.ghostEdges) { - Overlays.deleteOverlay(this.ghostEdges[ballId]); - delete this.ghostEdges[ballId]; - } -} - -BallController.prototype.onCleanup = function() { - HandController.prototype.onCleanup.call(this); - this.clearGhostEdges(); -} - -// FIXME resolve some of the issues with dual controllers before allowing both controllers active -var handControllers = [new BallController(LEFT_CONTROLLER)]; //, new HandController(RIGHT) ]; From 9d07bf1585ece2eb407abe53102d158d0953304f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 30 Aug 2015 19:20:47 -0700 Subject: [PATCH 28/29] Magballs: Removing entity scripts, using onUpdate --- examples/entityScripts/magBallEdge.js | 124 ------- examples/toys/magBalls.js | 306 ------------------ examples/toys/magBalls/ballController.js | 74 +++++ .../toys/{magSticks => magBalls}/constants.js | 72 +++++ examples/toys/magBalls/debugUtils.js | 95 ++++++ examples/toys/magBalls/edgeSpring.js | 45 +++ .../toys/{magSticks => magBalls}/graph.js | 4 + .../{magSticks => magBalls}/handController.js | 6 +- .../{magSticks => magBalls}/highlighter.js | 0 examples/toys/magBalls/magBalls.js | 289 +++++++++++++++++ examples/toys/magBalls/magBallsMain.js | 26 ++ .../toys/{magSticks => magBalls}/utils.js | 38 +-- 12 files changed, 619 insertions(+), 460 deletions(-) delete mode 100644 examples/entityScripts/magBallEdge.js delete mode 100644 examples/toys/magBalls.js create mode 100644 examples/toys/magBalls/ballController.js rename examples/toys/{magSticks => magBalls}/constants.js (50%) create mode 100644 examples/toys/magBalls/debugUtils.js create mode 100644 examples/toys/magBalls/edgeSpring.js rename examples/toys/{magSticks => magBalls}/graph.js (98%) rename examples/toys/{magSticks => magBalls}/handController.js (96%) rename examples/toys/{magSticks => magBalls}/highlighter.js (100%) create mode 100644 examples/toys/magBalls/magBalls.js create mode 100644 examples/toys/magBalls/magBallsMain.js rename examples/toys/{magSticks => magBalls}/utils.js (82%) diff --git a/examples/entityScripts/magBallEdge.js b/examples/entityScripts/magBallEdge.js deleted file mode 100644 index 5411a82088..0000000000 --- a/examples/entityScripts/magBallEdge.js +++ /dev/null @@ -1,124 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/08/29 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -(function(){ - this.preload = function(entityId) { - this.MIN_CHECK_INTERVAL = 0.05; - this.MAX_VARIANCE = 0.005; - this.ZERO_VECTOR = { x: 0, y: 0, z: 0 }; - - this.entityId = entityId; - var properties = Entities.getEntityProperties(this.entityId); - var userData = JSON.parse(properties.userData); - this.start = userData.magBalls.start; - this.end = userData.magBalls.end; - this.desiredLength = userData.magBalls.length; - this.timeSinceLastUpdate = 0; - this.nextCheckInterval = this.MIN_CHECK_INTERVAL; - - // FIXME do I really need to do this nonsense? - var _this = this; - this.updateWrapper = function(deltaTime) { - _this.onUpdate(deltaTime); - }; - Script.update.connect(this.updateWrapper); - - Script.scriptEnding.connect(function() { - _this.onCleanup(); - }); - Entities.deletingEntity.connect(function(entityId) { - _this.onCleanup(); - }); - }; - - this.onUpdate = function(deltaTime) { - this.timeSinceLastUpdate += deltaTime; - if (this.timeSinceLastUpdate > this.nextCheckInterval) { - this.updateProperties(); - this.timeSinceLastUpdate = 0; - var length = this.getLength(); - if (length == 0) { - this.onCleanup(); - return; - } - var variance = this.getVariance(length); - if (Math.abs(variance) <= this.MAX_VARIANCE) { - this.incrementCheckInterval(); - return; - } - this.decrementCheckInterval(); - var adjustmentVector = Vec3.multiply(variance / 4, this.vector); - var newPosition = Vec3.sum(Vec3.multiply(-1, adjustmentVector), this.position); - var newVector = Vec3.sum(Vec3.multiply(2, adjustmentVector), this.vector); - var newLength = Vec3.length(newVector); - var newVariance = this.getVariance(newLength); - Entities.editEntity(this.entityId, { - position: newPosition, - linePoints: [ this.ZERO_VECTOR, newVector ] - }); - Entities.editEntity(this.start, { - position: newPosition - }); - Entities.editEntity(this.end, { - position: Vec3.sum(newPosition, newVector) - }); - } - } - - this.incrementCheckInterval = function() { - this.nextCheckInterval = Math.min(this.nextCheckInterval * 2.0, 1.0); - } - - this.decrementCheckInterval = function() { - this.nextCheckInterval = 0.05; - } - - this.onCleanup = function() { - if (this.updateWrapper) { - Script.update.disconnect(this.updateWrapper); - delete this.updateWrapper; - } - } - - this.getVariance = function(length) { - if (!length) { - length = this.getLength(); - } - var difference = this.desiredLength - length; - return difference / this.desiredLength; - } - - this.getLength = function() { - return Vec3.length(this.vector); - } - - this.getPosition = function(entityId) { - var properties = Entities.getEntityProperties(entityId); - return properties.position; - } - - this.updateProperties = function() { - var properties = Entities.getEntityProperties(this.entityId); - var curStart = properties.position; - var curVector = properties.linePoints[1] - var curEnd = Vec3.sum(curVector, curStart); - var startPos = this.getPosition(this.start); - var endPos = this.getPosition(this.end); - var startError = Vec3.distance(curStart, startPos); - var endError = Vec3.distance(curEnd, endPos); - this.vector = Vec3.subtract(endPos, startPos); - if (startError > 0.005 || endError > 0.005) { - print("Fixing up edge"); - Entities.editEntity(this.entityId, { - position: startPos, - linePoints: [ this.ZERO_VECTOR, this.vector ] - }); - } - this.position = startPos; - } -}); diff --git a/examples/toys/magBalls.js b/examples/toys/magBalls.js deleted file mode 100644 index 46d4905d0e..0000000000 --- a/examples/toys/magBalls.js +++ /dev/null @@ -1,306 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/08/25 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -Script.include("magSticks/constants.js"); -Script.include("magSticks/utils.js"); -Script.include("magSticks/graph.js"); -Script.include("magSticks/highlighter.js"); -Script.include("magSticks/handController.js"); - -var BALL_NAME = "MagBall" -var EDGE_NAME = "MagStick" -var BALL_SELECTION_RADIUS = BALL_SIZE / 2.0 * 1.5; - -var BALL_DIMENSIONS = { - x: BALL_SIZE, - y: BALL_SIZE, - z: BALL_SIZE -}; - -var BALL_COLOR = { - red: 128, - green: 128, - blue: 128 -}; - -var STICK_DIMENSIONS = { - x: STICK_LENGTH / 6, - y: STICK_LENGTH / 6, - z: STICK_LENGTH -}; - -var BALL_DISTANCE = STICK_LENGTH + BALL_SIZE; - -var BALL_PROTOTYPE = { - type: "Sphere", - name: BALL_NAME, - dimensions: BALL_DIMENSIONS, - color: BALL_COLOR, - ignoreCollisions: true, - collisionsWillMove: false -}; - -// 2 millimeters -var EPSILON = (.002) / BALL_DISTANCE; - -var LINE_DIMENSIONS = { - x: 5, - y: 5, - z: 5 -} - -//var EDGE_ENTITY_SCRIPT_BASE = "file:/Users/bdavis/Git/hifi/examples/entityScripts"; -var EDGE_ENTITY_SCRIPT_BASE = "https://s3.amazonaws.com/hifi-public/scripts/entityScripts"; -var EDGE_ENTITY_SCRIPT = EDGE_ENTITY_SCRIPT_BASE + "/magBallEdge.js", - -var LINE_PROTOTYPE = { - type: "Line", - name: EDGE_NAME, - color: COLORS.CYAN, - dimensions: LINE_DIMENSIONS, - lineWidth: 5, - visible: true, - ignoreCollisions: true, - collisionsWillMove: false, - script: EDGE_ENTITY_SCRIPT -} - -var EDGE_PROTOTYPE = LINE_PROTOTYPE; - -// var EDGE_PROTOTYPE = { -// type: "Sphere", -// name: EDGE_NAME, -// color: { red: 0, green: 255, blue: 255 }, -// //dimensions: STICK_DIMENSIONS, -// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, -// rotation: rotation, -// visible: true, -// ignoreCollisions: true, -// collisionsWillMove: false -// } - - -// A collection of balls and edges connecting them. -MagBalls = function() { - this.selectedNodes = {}; - Graph.call(this); - Script.scriptEnding.connect(function() { - _this.onCleanup(); - }); -} - -MagBalls.prototype = Object.create( Graph.prototype ); - -MagBalls.prototype.onUpdate = function(deltaTime) { - // FIXME move to a physics based implementation as soon as bullet - // is exposed to entities -} - -MagBalls.prototype.createNodeEntity = function(customProperies) { - return Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies)); -} - -MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) { - var apos = this.getNodePosition(nodeIdA); - var bpos = this.getNodePosition(nodeIdB); - return Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, { - position: apos, - linePoints: [ ZERO_VECTOR, Vec3.subtract(bpos, apos) ], - userData: JSON.stringify({ - magBalls: { - start: nodeIdA, - end: nodeIdB, - length: BALL_DISTANCE - } - }) - })); -} - -MagBalls.prototype.findPotentialEdges = function(nodeId) { - var variances = {}; - for (var otherNodeId in this.nodes) { - // can't self connect - if (otherNodeId == nodeId) { - continue; - } - - // can't doubly connect - if (this.areConnected(otherNodeId, nodeId)) { - continue; - } - - // Too far to attempt - var distance = this.getNodeDistance(nodeId, otherNodeId); - var variance = this.getVariance(distance); - if (variance > 0.25) { - continue; - } - - variances[otherNodeId] = variance; - } - return variances; -} - -MagBalls.prototype.grabBall = function(position, maxDist) { - var selected = this.findNearestNode(position, maxDist); - if (!selected) { - selected = this.createNode({ position: position }); - } - if (selected) { - this.breakEdges(selected); - this.selectedNodes[selected] = true; - } - return selected; -} - -MagBalls.prototype.releaseBall = function(releasedBall) { - delete this.selectedNodes[releasedBall]; - logDebug("Released ball: " + releasedBall); - - // FIXME iterate through the other balls and ensure we don't intersect with - // any of them. If we do, just delete this ball and return. (play a pop - // sound) - - var releasePosition = this.getNodePosition(releasedBall); - - // Don't overlap other balls - for (var nodeId in this.nodes) { - if (nodeId == releasedBall) { - continue; - } - var distance = this.getNodeDistance(releasedBall, nodeId); - if (distance < BALL_SIZE / 2.0) { - this.destroyNode(nodeId); - return; - } - } - - var targets = this.findPotentialEdges(releasedBall); - for (var otherBallId in targets) { - this.createEdge(otherBallId, releasedBall); - } - this.clean(); - this.validate(); -} - - -MagBalls.prototype.getVariance = function(distance) { - // Given two points, how big is the difference between their distance - // and the desired length length - return (Math.abs(distance - BALL_DISTANCE)) / BALL_DISTANCE; -} - -// remove unconnected balls -MagBalls.prototype.clean = function() { - // do nothing unless there are at least 2 balls and one edge - if (Object.keys(this.nodes).length < 2 || !Object.keys(this.edges).length) { - return; - } - var disconnectedNodes = {}; - for (var nodeId in this.nodes) { - if (!Object.keys(this.nodes[nodeId]).length) { - disconnectedNodes[nodeId] = true; - } - } - for (var nodeId in disconnectedNodes) { - this.destroyNode(nodeId); - } -} - -// remove all balls -MagBalls.prototype.clear = function() { - if (DEBUG_MAGSTICKS) { - this.deleteAll(); - var ids = Entities.findEntities(MyAvatar.position, 50); - var result = []; - ids.forEach(function(id) { - var properties = Entities.getEntityProperties(id); - if (properties.name == BALL_NAME || properties.name == EDGE_NAME) { - Entities.deleteEntity(id); - } - }, this); - } -} - -var magBalls = new MagBalls(); - -// Clear any previous balls -// magBalls.clear(); - -// How close do we need to be to a ball to select it.... radius + 10% - -BallController = function(side) { - HandController.call(this, side); - this.highlighter = new Highlighter(); - this.highlighter.setSize(BALL_SIZE); - this.ghostEdges = {}; -} - -BallController.prototype = Object.create( HandController.prototype ); - -BallController.prototype.onUpdate = function(deltaTime) { - HandController.prototype.onUpdate.call(this, deltaTime); - if (!this.selected) { - // Find the highlight target and set it. - var target = magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(target); - return; - } - this.highlighter.highlight(null); - Entities.editEntity(this.selected, { position: this.tipPosition }); - var targetBalls = magBalls.findPotentialEdges(this.selected); - for (var ballId in targetBalls) { - if (!this.ghostEdges[ballId]) { - // create the ovleray - this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { - start: magBalls.getNodePosition(ballId), - end: this.tipPosition, - color: { red: 255, green: 0, blue: 0}, - alpha: 1, - lineWidth: 5, - visible: true, - }); - } else { - Overlays.editOverlay(this.ghostEdges[ballId], { - end: this.tipPosition, - }); - } - } - for (var ballId in this.ghostEdges) { - if (!targetBalls[ballId]) { - Overlays.deleteOverlay(this.ghostEdges[ballId]); - delete this.ghostEdges[ballId]; - } - } -} - -BallController.prototype.onClick = function() { - this.selected = magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(null); -} - -BallController.prototype.onRelease = function() { - this.clearGhostEdges(); - magBalls.releaseBall(this.selected); - this.selected = null; -} - -BallController.prototype.clearGhostEdges = function() { - for(var ballId in this.ghostEdges) { - Overlays.deleteOverlay(this.ghostEdges[ballId]); - delete this.ghostEdges[ballId]; - } -} - -BallController.prototype.onCleanup = function() { - HandController.prototype.onCleanup.call(this); - this.clearGhostEdges(); -} - -// FIXME resolve some of the issues with dual controllers before allowing both controllers active -var handControllers = [new BallController(LEFT_CONTROLLER)]; //, new HandController(RIGHT) ]; diff --git a/examples/toys/magBalls/ballController.js b/examples/toys/magBalls/ballController.js new file mode 100644 index 0000000000..62e2e0a4d0 --- /dev/null +++ b/examples/toys/magBalls/ballController.js @@ -0,0 +1,74 @@ +Script.include("handController.js"); +Script.include("highlighter.js"); + +BallController = function(side, magBalls) { + HandController.call(this, side); + this.magBalls = magBalls; + this.highlighter = new Highlighter(); + this.highlighter.setSize(BALL_SIZE); + this.ghostEdges = {}; + this.lastUpdate = 0; + this.updateInterval = 0.05; +} + +BallController.prototype = Object.create( HandController.prototype ); + +BallController.prototype.onUpdate = function(deltaTime) { + HandController.prototype.onUpdate.call(this, deltaTime); + + if (!this.selected) { + // Find the highlight target and set it. + var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(target); + return; + } + this.highlighter.highlight(null); + Entities.editEntity(this.selected, { position: this.tipPosition }); + var targetBalls = this.magBalls.findPotentialEdges(this.selected); + for (var ballId in targetBalls) { + if (!this.ghostEdges[ballId]) { + // create the ovleray + this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { + start: this.magBalls.getNodePosition(ballId), + end: this.tipPosition, + color: COLORS.RED, + alpha: 1, + lineWidth: 5, + visible: true, + }); + } else { + Overlays.editOverlay(this.ghostEdges[ballId], { + end: this.tipPosition, + }); + } + } + for (var ballId in this.ghostEdges) { + if (!targetBalls[ballId]) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } + } +} + +BallController.prototype.onClick = function() { + this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(null); +} + +BallController.prototype.onRelease = function() { + this.clearGhostEdges(); + this.magBalls.releaseBall(this.selected); + this.selected = null; +} + +BallController.prototype.clearGhostEdges = function() { + for(var ballId in this.ghostEdges) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } +} + +BallController.prototype.onCleanup = function() { + HandController.prototype.onCleanup.call(this); + this.clearGhostEdges(); +} diff --git a/examples/toys/magSticks/constants.js b/examples/toys/magBalls/constants.js similarity index 50% rename from examples/toys/magSticks/constants.js rename to examples/toys/magBalls/constants.js index 297fa51c6e..d154910f91 100644 --- a/examples/toys/magSticks/constants.js +++ b/examples/toys/magBalls/constants.js @@ -16,6 +16,10 @@ STICK_LENGTH = 0.24 * SCALE; DEBUG_MAGSTICKS = true; +CUSTOM_DATA_NAME = "magBalls"; +BALL_NAME = "MagBall"; +EDGE_NAME = "MagStick"; + ZERO_VECTOR = { x: 0, y: 0, z: 0 }; COLORS = { @@ -66,3 +70,71 @@ COLORS = { } } +BALL_RADIUS = BALL_SIZE / 2.0; + +BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5; + +BALL_DIMENSIONS = { + x: BALL_SIZE, + y: BALL_SIZE, + z: BALL_SIZE +}; + +BALL_COLOR = { + red: 128, + green: 128, + blue: 128 +}; + +STICK_DIMENSIONS = { + x: STICK_LENGTH / 6, + y: STICK_LENGTH / 6, + z: STICK_LENGTH +}; + +BALL_DISTANCE = STICK_LENGTH + BALL_SIZE; + +BALL_PROTOTYPE = { + type: "Sphere", + name: BALL_NAME, + dimensions: BALL_DIMENSIONS, + color: BALL_COLOR, + ignoreCollisions: true, + collisionsWillMove: false +}; + +// 2 millimeters +BALL_EPSILON = (.002) / BALL_DISTANCE; + +LINE_DIMENSIONS = { + x: 5, + y: 5, + z: 5 +} + +LINE_PROTOTYPE = { + type: "Line", + name: EDGE_NAME, + color: COLORS.CYAN, + dimensions: LINE_DIMENSIONS, + lineWidth: 5, + visible: true, + ignoreCollisions: true, + collisionsWillMove: false, +} + +EDGE_PROTOTYPE = LINE_PROTOTYPE; + +// var EDGE_PROTOTYPE = { +// type: "Sphere", +// name: EDGE_NAME, +// color: { red: 0, green: 255, blue: 255 }, +// //dimensions: STICK_DIMENSIONS, +// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, +// rotation: rotation, +// visible: true, +// ignoreCollisions: true, +// collisionsWillMove: false +// } + + diff --git a/examples/toys/magBalls/debugUtils.js b/examples/toys/magBalls/debugUtils.js new file mode 100644 index 0000000000..8dadd34679 --- /dev/null +++ b/examples/toys/magBalls/debugUtils.js @@ -0,0 +1,95 @@ +findMatchingNode = function(position, nodePositions) { + for (var nodeId in nodePositions) { + var nodePos = nodePositions[nodeId]; + var distance = Vec3.distance(position, nodePos); + if (distance < 0.03) { + return nodeId; + } + } +} + +repairConnections = function() { + var ids = Entities.findEntities(MyAvatar.position, 50); + + // Find all the balls and record their positions + var nodePositions = {}; + for (var i in ids) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == BALL_NAME) { + nodePositions[id] = properties.position; + } + } + + // Now check all the edges to see if they're valid (point to balls) + // and ensure that the balls point back to them + var ballsToEdges = {}; + for (var i in ids) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == EDGE_NAME) { + var startPos = properties.position; + var endPos = Vec3.sum(startPos, properties.linePoints[1]); + var magBallData = getMagBallsData(id); + var update = false; + if (!magBallData.start) { + var startNode = findMatchingNode(startPos, nodePositions); + if (startNode) { + logDebug("Found start node " + startNode) + magBallData.start = startNode; + update = true; + } + } + if (!magBallData.end) { + var endNode = findMatchingNode(endPos, nodePositions); + if (endNode) { + logDebug("Found end node " + endNode) + magBallData.end = endNode; + update = true; + } + } + if (!magBallData.start || !magBallData.end) { + logDebug("Didn't find both ends"); + Entities.deleteEntity(id); + continue; + } + if (!ballsToEdges[magBallData.start]) { + ballsToEdges[magBallData.start] = [ id ]; + } else { + ballsToEdges[magBallData.start].push(id); + } + if (!ballsToEdges[magBallData.end]) { + ballsToEdges[magBallData.end] = [ id ]; + } else { + ballsToEdges[magBallData.end].push(id); + } + if (update) { + logDebug("Updating incomplete edge " + id); + magBallData.length = BALL_DISTANCE; + setMagBallsData(id, magBallData); + } + } + } + for (var nodeId in ballsToEdges) { + var magBallData = getMagBallsData(nodeId); + var edges = magBallData.edges || []; + var edgeHash = {}; + for (var i in edges) { + edgeHash[edges[i]] = true; + } + var update = false; + for (var i in ballsToEdges[nodeId]) { + var edgeId = ballsToEdges[nodeId][i]; + if (!edgeHash[edgeId]) { + update = true; + edgeHash[edgeId] = true; + edges.push(edgeId); + } + } + if (update) { + logDebug("Fixing node with missing edge data"); + magBallData.edges = edges; + setMagBallsData(nodeId, magBallData); + } + } +} diff --git a/examples/toys/magBalls/edgeSpring.js b/examples/toys/magBalls/edgeSpring.js new file mode 100644 index 0000000000..852c9257c2 --- /dev/null +++ b/examples/toys/magBalls/edgeSpring.js @@ -0,0 +1,45 @@ + +EdgeSpring = function(edgeId, graph) { + this.edgeId = edgeId; + this.graph = graph; + + var magBallsData = getMagBallsData(this.edgeId); + this.start = magBallsData.start; + this.end = magBallsData.end; + this.desiredLength = magBallsData.length || BALL_DISTANCE; +} + +EdgeSpring.prototype.adjust = function(results) { + var startPos = this.getAdjustedPosition(this.start, results); + var endPos = this.getAdjustedPosition(this.end, results); + var vector = Vec3.subtract(endPos, startPos); + var length = Vec3.length(vector); + var variance = this.getVariance(length); + + if (Math.abs(variance) <= this.MAX_VARIANCE) { + return false; + } + + // adjust by halves until we fall below our variance + var adjustmentVector = Vec3.multiply(variance / 4, vector); + + var newStartPos = Vec3.sum(Vec3.multiply(-1, adjustmentVector), startPos); + var newEndPos = Vec3.sum(adjustmentVector, endPos); + results[this.start] = newStartPos; + results[this.end] = newEndPos; + return true; +} + +EdgeSpring.prototype.MAX_VARIANCE = 0.005; + +EdgeSpring.prototype.getAdjustedPosition = function(nodeId, results) { + if (results[nodeId]) { + return results[nodeId]; + } + return this.graph.getNodePosition(nodeId); +} + +EdgeSpring.prototype.getVariance = function(length) { + var difference = this.desiredLength - length; + return difference / this.desiredLength; +} diff --git a/examples/toys/magSticks/graph.js b/examples/toys/magBalls/graph.js similarity index 98% rename from examples/toys/magSticks/graph.js rename to examples/toys/magBalls/graph.js index 8ea9f97fc3..df02ee3628 100644 --- a/examples/toys/magSticks/graph.js +++ b/examples/toys/magBalls/graph.js @@ -16,12 +16,16 @@ Graph = function() { nodeId2: { edgeId1: true }, + // Nodes can many edges nodeId3: { edgeId2: true edgeId3: true edgeId4: true edgeId5: true }, + // Nodes can have 0 edges + nodeId5: { + }, ... } */ diff --git a/examples/toys/magSticks/handController.js b/examples/toys/magBalls/handController.js similarity index 96% rename from examples/toys/magSticks/handController.js rename to examples/toys/magBalls/handController.js index 57bef9be4a..3e54c7ed1b 100644 --- a/examples/toys/magSticks/handController.js +++ b/examples/toys/magBalls/handController.js @@ -54,7 +54,7 @@ HandController.prototype.setActive = function(active) { if (active == this.active) { return; } - logDebug("Setting active: " + active); + logDebug("Hand controller changing active state: " + active); this.active = active; Overlays.editOverlay(this.pointer, { visible: this.active @@ -68,12 +68,8 @@ HandController.prototype.updateControllerState = function() { this.palmPos = Controller.getSpatialControlPosition(this.palm); var tipPos = Controller.getSpatialControlPosition(this.tip); this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale); - // When on the base hydras report a position of 0 this.setActive(Vec3.length(this.palmPos) > 0.001); - if (!this.active) { - return; - } } HandController.prototype.onCleanup = function() { diff --git a/examples/toys/magSticks/highlighter.js b/examples/toys/magBalls/highlighter.js similarity index 100% rename from examples/toys/magSticks/highlighter.js rename to examples/toys/magBalls/highlighter.js diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js new file mode 100644 index 0000000000..9e0cbb4982 --- /dev/null +++ b/examples/toys/magBalls/magBalls.js @@ -0,0 +1,289 @@ +// +// Created by Bradley Austin Davis on 2015/08/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var UPDATE_INTERVAL = 0.1; + +Script.include("graph.js"); +Script.include("edgeSpring.js"); + +// A collection of balls and edges connecting them. +MagBalls = function() { + Graph.call(this); + + this.lastUpdateAge = 0; + this.stable = false; + this.selectedNodes = {}; + this.edgeObjects = {}; + + this.refresh(); + + var _this = this; + Script.update.connect(function(deltaTime) { + _this.onUpdate(deltaTime); + }); + + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); +} + +MagBalls.prototype = Object.create( Graph.prototype ); + +MagBalls.prototype.onUpdate = function(deltaTime) { + this.lastUpdateAge += deltaTime; + if (this.lastUpdateAge > UPDATE_INTERVAL) { + this.lastUpdateAge = 0; + if (!this.stable) { + // logDebug("Update"); + var adjusted = false; + var nodeAdjustResults = {}; + var fixupEdges = {}; + + for(var edgeId in this.edges) { + adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults); + } + for (var nodeId in nodeAdjustResults) { + var curPos = this.getNodePosition(nodeId); + var newPos = nodeAdjustResults[nodeId]; + var distance = Vec3.distance(curPos, newPos); + for (var edgeId in this.nodes[nodeId]) { + fixupEdges[edgeId] = true; + } + // logDebug("Moving node Id " + nodeId + " " + (distance * 1000).toFixed(3) + " mm"); + Entities.editEntity(nodeId, { position: newPos, color: COLORS.RED }); + } + + for (var edgeId in fixupEdges) { + this.fixupEdge(edgeId); + } + + Script.setTimeout(function(){ + for (var nodeId in nodeAdjustResults) { + Entities.editEntity(nodeId, { color: BALL_COLOR }); + } + }, ((UPDATE_INTERVAL * 1000) / 2)); + + if (!adjusted) { + this.stable = true; + } + } + } +} + +MagBalls.prototype.createNodeEntity = function(customProperies) { + var nodeId = Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies)); + return nodeId; +} + +MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) { + var apos = this.getNodePosition(nodeIdA); + var bpos = this.getNodePosition(nodeIdB); + var edgeId = Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, { + position: apos, + linePoints: [ ZERO_VECTOR, Vec3.subtract(bpos, apos) ], + userData: JSON.stringify({ + magBalls: { + start: nodeIdA, + end: nodeIdB, + length: BALL_DISTANCE + } + }) + })); + this.edgeObjects[edgeId] = new EdgeSpring(edgeId, this); + return edgeId; +} + +MagBalls.prototype.findPotentialEdges = function(nodeId) { + var variances = {}; + for (var otherNodeId in this.nodes) { + // can't self connect + if (otherNodeId == nodeId) { + continue; + } + + // can't doubly connect + if (this.areConnected(otherNodeId, nodeId)) { + continue; + } + + // Check distance to attempt + var distance = this.getNodeDistance(nodeId, otherNodeId); + var variance = this.getVariance(distance); + if (Math.abs(variance) > 0.25) { + continue; + } + + variances[otherNodeId] = variance; + } + return variances; +} + +MagBalls.prototype.grabBall = function(position, maxDist) { + var selected = this.findNearestNode(position, maxDist); + if (!selected) { + selected = this.createNode({ position: position }); + } + if (selected) { + this.breakEdges(selected); + this.selectedNodes[selected] = true; + } + return selected; +} + +MagBalls.prototype.releaseBall = function(releasedBall) { + delete this.selectedNodes[releasedBall]; + logDebug("Released ball: " + releasedBall); + + this.stable = false; + + var releasePosition = this.getNodePosition(releasedBall); + + // iterate through the other balls and ensure we don't intersect with + // any of them. If we do, just delete this ball and return. + // FIXME (play a pop sound) + for (var nodeId in this.nodes) { + if (nodeId == releasedBall) { + continue; + } + var distance = this.getNodeDistance(releasedBall, nodeId); + if (distance < BALL_SIZE) { + this.destroyNode(releasedBall); + return; + } + } + + var targets = this.findPotentialEdges(releasedBall); + if (!targets || !Object.keys(targets).length) { + this.destroyNode(releasedBall); + } + for (var otherBallId in targets) { + this.createEdge(otherBallId, releasedBall); + } +// this.clean(); + this.validate(); +} + + +MagBalls.prototype.getVariance = function(distance) { + // FIXME different balls or edges might have different ideas of variance... + // let something else handle this + var offset = (BALL_DISTANCE - distance); + var variance = offset / BALL_DISTANCE + return variance; +} + +// remove unconnected balls +MagBalls.prototype.clean = function() { + // do nothing unless there are at least 2 balls and one edge + if (Object.keys(this.nodes).length < 2 || !Object.keys(this.edges).length) { + return; + } + var disconnectedNodes = {}; + for (var nodeId in this.nodes) { + if (!Object.keys(this.nodes[nodeId]).length) { + disconnectedNodes[nodeId] = true; + } + } + for (var nodeId in disconnectedNodes) { + this.destroyNode(nodeId); + } +} + +// remove all balls +MagBalls.prototype.clear = function() { + if (DEBUG_MAGSTICKS) { + this.deleteAll(); + var ids = Entities.findEntities(MyAvatar.position, 50); + var result = []; + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id); + if (properties.name == BALL_NAME || properties.name == EDGE_NAME) { + Entities.deleteEntity(id); + } + }, this); + } +} + +MagBalls.prototype.destroyEdge = function(edgeId) { + Graph.prototype.destroyEdge.call(this, edgeId); + delete this.edgeObjects[edgeId]; +} + +MagBalls.prototype.destroyNode = function(nodeId) { + Graph.prototype.destroyNode.call(this, nodeId); +} + +// Scan the entity tree and load all the objects in range +MagBalls.prototype.refresh = function() { + var ids = Entities.findEntities(MyAvatar.position, 50); + for (var i in ids) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == BALL_NAME) { + this.nodes[id] = {}; + } + } + + var deleteEdges = []; + for (var i in ids) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id); + if (properties.name == EDGE_NAME) { + var edgeId = id; + this.edges[edgeId] = {}; + var magBallData = getMagBallsData(id); + if (!magBallData.start || !magBallData.end) { + logWarn("Edge information is missing for " + id); + continue; + } + if (!this.nodes[magBallData.start] || !this.nodes[magBallData.end]) { + logWarn("Edge " + id + " refers to unknown nodes: " + JSON.stringify(magBallData)); + Entities.editEntity(id, { color: COLORS.RED }); + deleteEdges.push(id); + continue; + } + this.nodes[magBallData.start][edgeId] = true; + this.nodes[magBallData.end][edgeId] = true; + this.edges[edgeId][magBallData.start] = true; + this.edges[edgeId][magBallData.end] = true; + this.edgeObjects[id] = new EdgeSpring(id, this); + } + } + + if (deleteEdges.length) { + Script.setTimeout(function() { + for (var i in deleteEdges) { + var edgeId = deleteEdges[i]; + logDebug("deleting invalid edge " + edgeId); + Entities.deleteEntity(edgeId); + } + }, 1000); + } + + var edgeCount = Object.keys(this.edges).length; + var nodeCount = Object.keys(this.nodes).length; + logDebug("Found " + nodeCount + " nodes and " + edgeCount + " edges "); + this.validate(); +} + + +MagBalls.prototype.findEdgeParams = function(startBall, endBall) { + var startBallPos = this.getNodePosition(startBall); + var endBallPos = this.getNodePosition(endBall); + var vector = Vec3.subtract(endBallPos, startBallPos); + return { + position: startBallPos, + linePoints: [ ZERO_VECTOR, vector ] + }; +} + +MagBalls.prototype.fixupEdge = function(edgeId) { + var ballsInEdge = Object.keys(this.edges[edgeId]); + Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1])); +} + diff --git a/examples/toys/magBalls/magBallsMain.js b/examples/toys/magBalls/magBallsMain.js new file mode 100644 index 0000000000..1c6bd2b159 --- /dev/null +++ b/examples/toys/magBalls/magBallsMain.js @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis on 2015/08/25 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("constants.js"); +Script.include("utils.js"); +Script.include("magBalls.js"); + +Script.include("ballController.js"); + +var magBalls = new MagBalls(); + +// Clear any previous balls +// magBalls.clear(); + +MenuController = function(side) { + HandController.call(this, side); +} + + +// FIXME resolve some of the issues with dual controllers before allowing both controllers active +var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ]; diff --git a/examples/toys/magSticks/utils.js b/examples/toys/magBalls/utils.js similarity index 82% rename from examples/toys/magSticks/utils.js rename to examples/toys/magBalls/utils.js index 8a522ac41f..ea1446f858 100644 --- a/examples/toys/magSticks/utils.js +++ b/examples/toys/magBalls/utils.js @@ -28,27 +28,6 @@ findAction = function(name) { return 0; } - -var LINE_DIMENSIONS = { - x: 5, - y: 5, - z: 5 -} - -var EDGE_NAME = "MagStick" - -var LINE_PROTOTYPE = { - type: "Line", - name: EDGE_NAME, - color: COLORS.CYAN, - dimensions: LINE_DIMENSIONS, - lineWidth: 5, - visible: true, - ignoreCollisions: true, - collisionsWillMove: false -} - - addLine = function(origin, vector, color) { if (!color) { color = COLORS.WHITE @@ -65,7 +44,8 @@ addLine = function(origin, vector, color) { // FIXME fetch from a subkey of user data to support non-destructive modifications setEntityUserData = function(id, data) { - Entities.editEntity(id, { userData: JSON.stringify(data) }); + var json = JSON.stringify(data) + Entities.editEntity(id, { userData: json }); } // FIXME do non-destructive modification of the existing user data @@ -73,9 +53,9 @@ getEntityUserData = function(id) { var results = null; var properties = Entities.getEntityProperties(id); if (properties.userData) { - results = JSON.parse(this.properties.userData); + results = JSON.parse(properties.userData); } - return results; + return results ? results : {}; } // Non-destructively modify the user data of an entity. @@ -86,10 +66,18 @@ setEntityCustomData = function(customKey, id, data) { } getEntityCustomData = function(customKey, id, defaultValue) { - var userData = getEntityUserData(); + var userData = getEntityUserData(id); return userData[customKey] ? userData[customKey] : defaultValue; } +getMagBallsData = function(id) { + return getEntityCustomData(CUSTOM_DATA_NAME, id, {}); +} + +setMagBallsData = function(id, value) { + setEntityCustomData(CUSTOM_DATA_NAME, id, value); +} + mergeObjects = function(proto, custom) { var result = {}; for (var attrname in proto) { From dc7d20ff86a75a6c9228d4dee4646e9645ce8278 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 30 Aug 2015 23:49:47 -0700 Subject: [PATCH 29/29] Working on ui --- examples/toys/magBalls/ballController.js | 33 +++++++++++- examples/toys/magBalls/handController.js | 32 +++++++++++- examples/toys/magBalls/magBalls.js | 12 +++-- examples/toys/magBalls/magBallsMain.js | 1 - examples/toys/magBalls/menuController.js | 66 ++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 examples/toys/magBalls/menuController.js diff --git a/examples/toys/magBalls/ballController.js b/examples/toys/magBalls/ballController.js index 62e2e0a4d0..0f178b2804 100644 --- a/examples/toys/magBalls/ballController.js +++ b/examples/toys/magBalls/ballController.js @@ -7,8 +7,6 @@ BallController = function(side, magBalls) { this.highlighter = new Highlighter(); this.highlighter.setSize(BALL_SIZE); this.ghostEdges = {}; - this.lastUpdate = 0; - this.updateInterval = 0.05; } BallController.prototype = Object.create( HandController.prototype ); @@ -72,3 +70,34 @@ BallController.prototype.onCleanup = function() { HandController.prototype.onCleanup.call(this); this.clearGhostEdges(); } + +BallController.prototype.onAltClick = function() { + return; + var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); + if (!target) { + logDebug(target); + return; + } + + // FIXME move to delete shape + var toDelete = {}; + var deleteQueue = [ target ]; + while (deleteQueue.length) { + var curNode = deleteQueue.shift(); + if (toDelete[curNode]) { + continue; + } + toDelete[curNode] = true; + for (var nodeId in this.magBalls.getConnectedNodes(curNode)) { + deleteQueue.push(nodeId); + } + } + for (var nodeId in toDelete) { + this.magBalls.destroyNode(nodeId); + } +} + + + +BallController.prototype.onAltRelease = function() { +} diff --git a/examples/toys/magBalls/handController.js b/examples/toys/magBalls/handController.js index 3e54c7ed1b..998d22c6f8 100644 --- a/examples/toys/magBalls/handController.js +++ b/examples/toys/magBalls/handController.js @@ -14,6 +14,7 @@ HandController = function(side) { this.palm = 2 * side; this.tip = 2 * side + 1; this.action = findAction(side ? "ACTION2" : "ACTION1"); + this.altAction = findAction(side ? "ACTION1" : "ACTION2"); this.active = false; this.tipScale = 1.4; this.pointer = Overlays.addOverlay("sphere", { @@ -41,6 +42,10 @@ HandController = function(side) { } HandController.prototype.onActionEvent = function(action, state) { + var spatialControlCount = Controller.getNumberOfSpatialControls(); + // If only 2 spacial controls, then we only have one controller active, so use either button + // otherwise, only use the specified action + if (action == this.action) { if (state) { this.onClick(); @@ -48,6 +53,14 @@ HandController.prototype.onActionEvent = function(action, state) { this.onRelease(); } } + + if (action == this.altAction) { + if (state) { + this.onAltClick(); + } else { + this.onAltRelease(); + } + } } HandController.prototype.setActive = function(active) { @@ -65,11 +78,18 @@ HandController.prototype.setActive = function(active) { } HandController.prototype.updateControllerState = function() { + // FIXME this returns data if either the left or right controller is not on the base this.palmPos = Controller.getSpatialControlPosition(this.palm); var tipPos = Controller.getSpatialControlPosition(this.tip); this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale); - // When on the base hydras report a position of 0 + // When on the base, hydras report a position of 0 this.setActive(Vec3.length(this.palmPos) > 0.001); + + //logDebug(Controller.getTriggerValue(0) + " " + Controller.getTriggerValue(1)); + + //if (this.active) { + // logDebug("#ctrls " + Controller.getNumberOfSpatialControls() + " Side: " + this.side + " Palm: " + this.palm + " " + vec3toStr(this.palmPos)) + //} } HandController.prototype.onCleanup = function() { @@ -95,3 +115,13 @@ HandController.prototype.onClick = function() { HandController.prototype.onRelease = function() { logDebug("Base hand controller does nothing on release"); } + +HandController.prototype.onAltClick = function() { + logDebug("Base hand controller does nothing on alt click"); +} + +HandController.prototype.onAltRelease = function() { + logDebug("Base hand controller does nothing on alt click"); +} + + diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js index 9e0cbb4982..187c550073 100644 --- a/examples/toys/magBalls/magBalls.js +++ b/examples/toys/magBalls/magBalls.js @@ -15,8 +15,10 @@ Script.include("edgeSpring.js"); MagBalls = function() { Graph.call(this); + this.MAX_ADJUST_ITERATIONS = 100; this.lastUpdateAge = 0; this.stable = false; + this.adjustIterations = 0; this.selectedNodes = {}; this.edgeObjects = {}; @@ -39,6 +41,7 @@ MagBalls.prototype.onUpdate = function(deltaTime) { if (this.lastUpdateAge > UPDATE_INTERVAL) { this.lastUpdateAge = 0; if (!this.stable) { + this.adjustIterations += 1; // logDebug("Update"); var adjusted = false; var nodeAdjustResults = {}; @@ -68,9 +71,10 @@ MagBalls.prototype.onUpdate = function(deltaTime) { } }, ((UPDATE_INTERVAL * 1000) / 2)); - if (!adjusted) { + if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) { + this.adjustIterations = 0; this.stable = true; - } + } } } } @@ -129,6 +133,7 @@ MagBalls.prototype.grabBall = function(position, maxDist) { selected = this.createNode({ position: position }); } if (selected) { + this.stable = true; this.breakEdges(selected); this.selectedNodes[selected] = true; } @@ -159,12 +164,11 @@ MagBalls.prototype.releaseBall = function(releasedBall) { var targets = this.findPotentialEdges(releasedBall); if (!targets || !Object.keys(targets).length) { - this.destroyNode(releasedBall); +// this.destroyNode(releasedBall); } for (var otherBallId in targets) { this.createEdge(otherBallId, releasedBall); } -// this.clean(); this.validate(); } diff --git a/examples/toys/magBalls/magBallsMain.js b/examples/toys/magBalls/magBallsMain.js index 1c6bd2b159..e54b818e4a 100644 --- a/examples/toys/magBalls/magBallsMain.js +++ b/examples/toys/magBalls/magBallsMain.js @@ -21,6 +21,5 @@ MenuController = function(side) { HandController.call(this, side); } - // FIXME resolve some of the issues with dual controllers before allowing both controllers active var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ]; diff --git a/examples/toys/magBalls/menuController.js b/examples/toys/magBalls/menuController.js new file mode 100644 index 0000000000..0a076d1ff8 --- /dev/null +++ b/examples/toys/magBalls/menuController.js @@ -0,0 +1,66 @@ +Script.include("handController.js"); + +MenuController = function(side, magBalls) { + HandController.call(this, side); +} + +MenuController.prototype = Object.create( HandController.prototype ); + +MenuController.prototype.onUpdate = function(deltaTime) { + HandController.prototype.onUpdate.call(this, deltaTime); + if (!this.selected) { + // Find the highlight target and set it. + var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(target); + return; + } + this.highlighter.highlight(null); + Entities.editEntity(this.selected, { position: this.tipPosition }); + var targetBalls = this.magBalls.findPotentialEdges(this.selected); + for (var ballId in targetBalls) { + if (!this.ghostEdges[ballId]) { + // create the ovleray + this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { + start: this.magBalls.getNodePosition(ballId), + end: this.tipPosition, + color: COLORS.RED, + alpha: 1, + lineWidth: 5, + visible: true, + }); + } else { + Overlays.editOverlay(this.ghostEdges[ballId], { + end: this.tipPosition, + }); + } + } + for (var ballId in this.ghostEdges) { + if (!targetBalls[ballId]) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } + } +} + +MenuController.prototype.onClick = function() { + this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(null); +} + +MenuController.prototype.onRelease = function() { + this.clearGhostEdges(); + this.magBalls.releaseBall(this.selected); + this.selected = null; +} + +MenuController.prototype.clearGhostEdges = function() { + for(var ballId in this.ghostEdges) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } +} + +MenuController.prototype.onCleanup = function() { + HandController.prototype.onCleanup.call(this); + this.clearGhostEdges(); +}