diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 73444d1198..d61a1a49b7 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -344,8 +344,6 @@ void Agent::scriptRequestFinished() { void Agent::executeScript() { _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); - DependencyManager::get()->setScriptEngine(_scriptEngine); - // setup an Avatar for the script to use auto scriptedAvatar = DependencyManager::get(); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 8b86ba5eb2..868e570e0c 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -522,11 +522,8 @@ void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID; } } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityID + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID << "More info:" << jsonObject; - tree->withWriteLock([&] { - tree->deleteEntity(entityID, true); - }); } networkReply->deleteLater(); diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 0a2dcb951b..a88a584c63 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -480,7 +480,7 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: { - popup.showSpecifyAvatarUrl(function() { + popup.showSpecifyAvatarUrl(currentAvatar.avatarUrl, function() { var url = popup.inputText.text; emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url}) }, function(link) { diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml index 125b30fa95..fd65d014a0 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml @@ -3,7 +3,7 @@ import QtQuick 2.5 MessageBox { id: popup - function showSpecifyAvatarUrl(callback, linkCallback) { + function showSpecifyAvatarUrl(url, callback, linkCallback) { popup.onButton2Clicked = callback; popup.titleText = 'Specify Avatar URL' popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.
' + @@ -12,6 +12,8 @@ MessageBox { '' popup.inputText.visible = true; popup.inputText.placeholderText = 'Enter Avatar Url'; + popup.inputText.text = url; + popup.inputText.selectAll(); popup.button1text = 'CANCEL'; popup.button2text = 'CONFIRM'; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b30b7c8d9d..f1c3b72991 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4844,12 +4844,6 @@ void Application::loadSettings() { isFirstPerson = (qApp->isHMDMode()); - // Flying should be disabled by default in HMD mode on first run, and it - // should be enabled by default in desktop mode. - - auto myAvatar = getMyAvatar(); - myAvatar->setFlyingEnabled(!isFirstPerson); - } else { // if this is not the first run, the camera will be initialized differently depending on user settings @@ -6539,9 +6533,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe entityScriptingInterface->setPacketSender(&_entityEditSender); entityScriptingInterface->setEntityTree(getEntities()->getTree()); - // give the script engine to the RecordingScriptingInterface for its callbacks - DependencyManager::get()->setScriptEngine(scriptEngine); - if (property(hifi::properties::TEST).isValid()) { scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance()); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bf7276d5d5..e87dcc85cd 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -577,9 +577,11 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); - + animateScaleChanges(deltaTime); + setFlyingEnabled(getFlyingEnabled()); + if (_cauterizationNeedsUpdate) { _cauterizationNeedsUpdate = false; @@ -724,16 +726,18 @@ void MyAvatar::simulate(float deltaTime) { properties.setQueryAACubeDirty(); properties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, + entity->getID(), properties); entity->setLastBroadcast(usecTimestampNow()); entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { - EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); - if (!entityDescendant->getClientOnly() && descendant->updateQueryAACube()) { + EntityItemPointer entityDescendant = std::dynamic_pointer_cast(descendant); + if (entityDescendant && !entityDescendant->getClientOnly() && descendant->updateQueryAACube()) { EntityItemProperties descendantProperties; descendantProperties.setQueryAACube(descendant->getQueryAACube()); descendantProperties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityDescendant->getID(), descendantProperties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, + entityDescendant->getID(), descendantProperties); entityDescendant->setLastBroadcast(now); // for debug/physics status icons } }); @@ -1131,6 +1135,8 @@ void MyAvatar::saveData() { settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); settings.setValue("userHeight", getUserHeight()); + settings.setValue("flyingDesktop", getFlyingDesktopPref()); + settings.setValue("flyingHMD", getFlyingHMDPref()); settings.setValue("enabledFlying", getFlyingEnabled()); settings.endGroup(); @@ -1281,8 +1287,13 @@ void MyAvatar::loadData() { settings.remove("avatarEntityData"); } setAvatarEntityDataChanged(true); + + // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle firstRunVal { Settings::firstRun, true }; - setFlyingEnabled(firstRunVal.get() ? getFlyingEnabled() : settings.value("enabledFlying").toBool()); + setFlyingDesktopPref(firstRunVal.get() ? true : settings.value("flyingDesktop").toBool()); + setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool()); + setFlyingEnabled(getFlyingEnabled()); + setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); @@ -2825,6 +2836,12 @@ void MyAvatar::setFlyingEnabled(bool enabled) { return; } + if (qApp->isHMDMode()) { + setFlyingHMDPref(enabled); + } else { + setFlyingDesktopPref(enabled); + } + _enableFlying = enabled; } @@ -2840,7 +2857,33 @@ bool MyAvatar::isInAir() { bool MyAvatar::getFlyingEnabled() { // May return true even if client is not allowed to fly in the zone. - return _enableFlying; + return (qApp->isHMDMode() ? getFlyingHMDPref() : getFlyingDesktopPref()); +} + +void MyAvatar::setFlyingDesktopPref(bool enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setFlyingDesktopPref", Q_ARG(bool, enabled)); + return; + } + + _flyingPrefDesktop = enabled; +} + +bool MyAvatar::getFlyingDesktopPref() { + return _flyingPrefDesktop; +} + +void MyAvatar::setFlyingHMDPref(bool enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setFlyingHMDPref", Q_ARG(bool, enabled)); + return; + } + + _flyingPrefHMD = enabled; +} + +bool MyAvatar::getFlyingHMDPref() { + return _flyingPrefHMD; } // Public interface for targetscale @@ -3042,7 +3085,7 @@ static glm::vec3 dampenCgMovement(glm::vec3 cgUnderHeadHandsAvatarSpace, float b } // computeCounterBalance returns the center of gravity in Avatar space -glm::vec3 MyAvatar::computeCounterBalance() const { +glm::vec3 MyAvatar::computeCounterBalance() { struct JointMass { QString name; float weight; @@ -3060,7 +3103,8 @@ glm::vec3 MyAvatar::computeCounterBalance() const { JointMass cgLeftHandMass(QString("LeftHand"), DEFAULT_AVATAR_LEFTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); JointMass cgRightHandMass(QString("RightHand"), DEFAULT_AVATAR_RIGHTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); glm::vec3 tposeHead = DEFAULT_AVATAR_HEAD_POS; - glm::vec3 tposeHips = glm::vec3(0.0f, 0.0f, 0.0f); + glm::vec3 tposeHips = DEFAULT_AVATAR_HIPS_POS; + glm::vec3 tposeRightFoot = DEFAULT_AVATAR_RIGHTFOOT_POS; if (_skeletonModel->getRig().indexOfJoint(cgHeadMass.name) != -1) { cgHeadMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name)); @@ -3079,6 +3123,9 @@ glm::vec3 MyAvatar::computeCounterBalance() const { if (_skeletonModel->getRig().indexOfJoint("Hips") != -1) { tposeHips = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("Hips")); } + if (_skeletonModel->getRig().indexOfJoint("RightFoot") != -1) { + tposeRightFoot = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot")); + } // find the current center of gravity position based on head and hand moments glm::vec3 sumOfMoments = (cgHeadMass.weight * cgHeadMass.position) + (cgLeftHandMass.weight * cgLeftHandMass.position) + (cgRightHandMass.weight * cgRightHandMass.position); @@ -3099,9 +3146,12 @@ glm::vec3 MyAvatar::computeCounterBalance() const { glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead; // find the height of the hips + const float UPPER_LEG_FRACTION = 0.3333f; glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z)); float headMinusHipXz = glm::length(xzDiff); float headHipDefault = glm::length(tposeHead - tposeHips); + float hipFootDefault = tposeHips.y - tposeRightFoot.y; + float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault); float hipHeight = 0.0f; if (headHipDefault > headMinusHipXz) { hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz)); @@ -3113,6 +3163,10 @@ glm::vec3 MyAvatar::computeCounterBalance() const { if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { // if the height is higher than default hips, clamp to default hips counterBalancedCg.y = tposeHips.y + 0.05f; + } else if (counterBalancedCg.y < sitSquatThreshold) { + //do a height reset + setResetMode(true); + _follow.activate(FollowHelper::Vertical); } return counterBalancedCg; } @@ -3162,7 +3216,7 @@ static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::ma // this function finds the hips position using a center of gravity model that // balances the head and hands with the hips over the base of support // returns the rotation (-z forward) and position of the Avatar in Sensor space -glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { +glm::mat4 MyAvatar::deriveBodyUsingCgModel() { glm::mat4 sensorToWorldMat = getSensorToWorldMatrix(); glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat); auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); @@ -3180,7 +3234,7 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { } // get the new center of gravity - const glm::vec3 cgHipsPosition = computeCounterBalance(); + glm::vec3 cgHipsPosition = computeCounterBalance(); // find the new hips rotation using the new head-hips axis as the up axis glm::mat4 avatarHipsMat = computeNewHipsMatrix(glmExtractRotation(avatarHeadMat), extractTranslation(avatarHeadMat), cgHipsPosition); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2ef6c2041b..d36e43ca25 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -955,6 +955,30 @@ public: */ Q_INVOKABLE bool getFlyingEnabled(); + /**jsdoc + * @function MyAvatar.setFlyingDesktopPref + * @param {boolean} enabled + */ + Q_INVOKABLE void setFlyingDesktopPref(bool enabled); + + /**jsdoc + * @function MyAvatar.getFlyingDesktopPref + * @returns {boolean} + */ + Q_INVOKABLE bool getFlyingDesktopPref(); + + /**jsdoc + * @function MyAvatar.setFlyingDesktopPref + * @param {boolean} enabled + */ + Q_INVOKABLE void setFlyingHMDPref(bool enabled); + + /**jsdoc + * @function MyAvatar.getFlyingDesktopPref + * @returns {boolean} + */ + Q_INVOKABLE bool getFlyingHMDPref(); + /**jsdoc * @function MyAvatar.getAvatarScale @@ -1019,12 +1043,12 @@ public: // results are in sensor frame (-z forward) glm::mat4 deriveBodyFromHMDSensor() const; - glm::vec3 computeCounterBalance() const; + glm::vec3 computeCounterBalance(); // derive avatar body position and orientation from using the current HMD Sensor location in relation to the previous // location of the base of support of the avatar. // results are in sensor frame (-z foward) - glm::mat4 deriveBodyUsingCgModel() const; + glm::mat4 deriveBodyUsingCgModel(); /**jsdoc * @function MyAvatar.isUp @@ -1505,6 +1529,8 @@ private: std::bitset _disabledDriveKeys; bool _enableFlying { false }; + bool _flyingPrefDesktop { true }; + bool _flyingPrefHMD { false }; bool _wasPushing { false }; bool _isPushing { false }; bool _isBeingPushed { false }; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 135b59a152..50a4d17cae 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -313,8 +313,8 @@ void setupPreferences() { getter, setter)); } { - auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); }; - auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); }; + auto getter = [=]()->bool { return myAvatar->getFlyingHMDPref(); }; + auto setter = [=](bool value) { myAvatar->setFlyingHMDPref(value); }; preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping", getter, setter)); } { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d8ac3dc63e..3f3c751943 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -699,14 +699,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { adjustShapeInfoByRegistration(shapeInfo); } -void RenderableModelEntityItem::setCollisionShape(const btCollisionShape* shape) { - const void* key = static_cast(shape); - if (_collisionMeshKey != key) { - _collisionMeshKey = key; - emit requestCollisionGeometryUpdate(); - } -} - void RenderableModelEntityItem::setJointMap(std::vector jointMap) { if (jointMap.size() > 0) { _jointMap = jointMap; @@ -1278,10 +1270,6 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return false; } -void ModelEntityRenderer::setCollisionMeshKey(const void*key) { - _collisionMeshKey = key; -} - void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); if (_hasModel != entity->hasModel()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 91e5496b97..7a6c882c5a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -78,8 +78,6 @@ public: virtual bool isReadyToComputeShape() const override; virtual void computeShapeInfo(ShapeInfo& shapeInfo) override; - void setCollisionShape(const btCollisionShape* shape) override; - virtual bool contains(const glm::vec3& point) const override; void stopModelOverrideIfNoParent(); @@ -112,10 +110,6 @@ public: virtual QStringList getJointNames() const override; bool getMeshes(MeshProxyList& result) override; // deprecated - const void* getCollisionMeshKey() const { return _collisionMeshKey; } - -signals: - void requestCollisionGeometryUpdate(); private: bool needsUpdateModelBounds() const; @@ -130,7 +124,6 @@ private: QVariantMap _originalTextures; bool _dimensionsInitialized { true }; bool _needsJointSimulation { false }; - const void* _collisionMeshKey { nullptr }; }; namespace render { namespace entities { @@ -161,7 +154,6 @@ protected: virtual bool needsRenderUpdate() const override; virtual void doRender(RenderArgs* args) override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - void setCollisionMeshKey(const void* key); render::hifi::Tag getTagMask() const override; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 309671f49e..73f46245c4 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -101,6 +101,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi _timeUntilNextEmit = 0; withWriteLock([&]{ _particleProperties = newParticleProperties; + if (!_prevEmitterShouldTrailInitialized) { + _prevEmitterShouldTrailInitialized = true; + _prevEmitterShouldTrail = _particleProperties.emission.shouldTrail; + } }); } _emitting = entity->getIsEmitting(); @@ -144,7 +148,12 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn particleUniforms.color.middle = _particleProperties.getColorMiddle(); particleUniforms.color.finish = _particleProperties.getColorFinish(); particleUniforms.color.spread = _particleProperties.getColorSpread(); + particleUniforms.spin.start = _particleProperties.spin.range.start; + particleUniforms.spin.middle = _particleProperties.spin.gradient.target; + particleUniforms.spin.finish = _particleProperties.spin.range.finish; + particleUniforms.spin.spread = _particleProperties.spin.gradient.spread; particleUniforms.lifespan = _particleProperties.lifespan; + particleUniforms.rotateWithEntity = _particleProperties.rotateWithEntity ? 1 : 0; }); // Update particle uniforms memcpy(&_uniformBuffer.edit(), &particleUniforms, sizeof(ParticleUniforms)); @@ -176,7 +185,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa const auto& azimuthFinish = particleProperties.azimuth.finish; const auto& emitDimensions = particleProperties.emission.dimensions; const auto& emitAcceleration = particleProperties.emission.acceleration.target; - auto emitOrientation = particleProperties.emission.orientation; + auto emitOrientation = baseTransform.getRotation() * particleProperties.emission.orientation; const auto& emitRadiusStart = glm::max(particleProperties.radiusStart, EPSILON); // Avoid math complications at center const auto& emitSpeed = particleProperties.emission.speed.target; const auto& speedSpread = particleProperties.emission.speed.spread; @@ -185,10 +194,9 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa particle.seed = randFloatInRange(-1.0f, 1.0f); particle.expiration = now + (uint64_t)(particleProperties.lifespan * USECS_PER_SECOND); - if (particleProperties.emission.shouldTrail) { - particle.position = baseTransform.getTranslation(); - emitOrientation = baseTransform.getRotation() * emitOrientation; - } + + particle.relativePosition = glm::vec3(0.0f); + particle.basePosition = baseTransform.getTranslation(); // Position, velocity, and acceleration if (polarStart == 0.0f && polarFinish == 0.0f && emitDimensions.z == 0.0f) { @@ -237,7 +245,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f, radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f )); - particle.position += emitOrientation * emitPosition; + particle.relativePosition += emitOrientation * emitPosition; } particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); @@ -262,8 +270,8 @@ void ParticleEffectEntityRenderer::stepSimulation() { particleProperties = _particleProperties; }); + const auto& modelTransform = getModelTransform(); if (_emitting && particleProperties.emitting()) { - const auto& modelTransform = getModelTransform(); uint64_t emitInterval = particleProperties.emitIntervalUsecs(); if (emitInterval > 0 && interval >= _timeUntilNextEmit) { auto timeRemaining = interval; @@ -288,15 +296,23 @@ void ParticleEffectEntityRenderer::stepSimulation() { const float deltaTime = (float)interval / (float)USECS_PER_SECOND; // update the particles for (auto& particle : _cpuParticles) { + if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) { + if (_prevEmitterShouldTrail) { + particle.relativePosition = particle.relativePosition + particle.basePosition - modelTransform.getTranslation(); + } + particle.basePosition = modelTransform.getTranslation(); + } particle.integrate(deltaTime); } + _prevEmitterShouldTrail = particleProperties.emission.shouldTrail; // Build particle primitives static GpuParticles gpuParticles; gpuParticles.clear(); gpuParticles.reserve(_cpuParticles.size()); // Reserve space - std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [](const CpuParticle& particle) { - return GpuParticle(particle.position, glm::vec2(particle.lifetime, particle.seed)); + std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) { + glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation()); + return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed)); }); // Update particle buffer @@ -324,15 +340,11 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { } Transform transform; - // In trail mode, the particles are created in world space. - // so we only set a transform if they're not in trail mode - if (!_particleProperties.emission.shouldTrail) { - - withReadLock([&] { - transform = _renderTransform; - }); - transform.setScale(vec3(1)); - } + // The particles are in world space, so the transform is unused, except for the rotation, which we use + // if the particles are marked rotateWithEntity + withReadLock([&] { + transform.setRotation(_renderTransform.getRotation()); + }); batch.setModelTransform(transform); batch.setUniformBuffer(PARTICLE_UNIFORM_SLOT, _uniformBuffer); batch.setInputFormat(_vertexFormat); @@ -341,5 +353,3 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle); batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE); } - - diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 8e9353894a..7655918c58 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -49,13 +49,14 @@ private: float seed { 0.0f }; uint64_t expiration { 0 }; float lifetime { 0.0f }; - glm::vec3 position; + glm::vec3 basePosition; + glm::vec3 relativePosition; glm::vec3 velocity; glm::vec3 acceleration; void integrate(float deltaTime) { glm::vec3 atSquared = (0.5f * deltaTime * deltaTime) * acceleration; - position += velocity * deltaTime + atSquared; + relativePosition += velocity * deltaTime + atSquared; velocity += acceleration * deltaTime; lifetime += deltaTime; } @@ -74,15 +75,18 @@ private: struct ParticleUniforms { InterpolationData radius; InterpolationData color; // rgba + InterpolationData spin; float lifespan; - glm::vec3 spare; + int rotateWithEntity; + glm::vec2 spare; }; - static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties); void stepSimulation(); particle::Properties _particleProperties; + bool _prevEmitterShouldTrail; + bool _prevEmitterShouldTrailInitialized { false }; CpuParticles _cpuParticles; bool _emitting { false }; uint64_t _timeUntilNextEmit { 0 }; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 793b4aa158..004a965c28 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -178,10 +178,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene withWriteLock([&] { - if (_contentType == ContentType::NoContent) { - return; - } - // This work must be done on the main thread // If we couldn't create a new web surface, exit if (!hasWebSurface() && !buildWebSurface(entity)) { @@ -315,7 +311,13 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { }); } else if (_contentType == ContentType::QmlContent) { _webSurface->load(_lastSourceUrl); + } else if (_contentType == ContentType::NoContent) { + // Show empty white panel + _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + item->setProperty(URL_PROPERTY, ""); + }); } + _fadeStartTime = usecTimestampNow(); _webSurface->resume(); diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index 7653bc0a42..22254c0ab0 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -27,11 +27,20 @@ struct Colors { vec4 finish; vec4 spread; }; +struct Spin { + float start; + float middle; + float finish; + float spread; +}; struct ParticleUniforms { Radii radius; Colors color; - vec4 lifespan; // x is lifespan, 3 spare floats + Spin spin; + float lifespan; + int rotateWithEntity; + vec2 spare; }; layout(std140) uniform particleBuffer { @@ -44,15 +53,6 @@ layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed out vec4 varColor; out vec2 varTexcoord; -const int NUM_VERTICES_PER_PARTICLE = 4; -// This ordering ensures that un-rotated particles render upright in the viewer. -const vec4 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec4[NUM_VERTICES_PER_PARTICLE]( - vec4(-1.0, 1.0, 0.0, 0.0), - vec4(-1.0, -1.0, 0.0, 0.0), - vec4(1.0, 1.0, 0.0, 0.0), - vec4(1.0, -1.0, 0.0, 0.0) -); - float bezierInterpolate(float y1, float y2, float y3, float u) { // https://en.wikipedia.org/wiki/Bezier_curve return (1.0 - u) * (1.0 - u) * y1 + 2.0 * (1.0 - u) * u * y2 + u * u * y3; @@ -103,6 +103,15 @@ vec4 interpolate3Vec4(vec4 y1, vec4 y2, vec4 y3, float u) { interpolate3Points(y1.w, y2.w, y3.w, u)); } +const int NUM_VERTICES_PER_PARTICLE = 4; +const vec2 TEX_COORDS[NUM_VERTICES_PER_PARTICLE] = vec2[NUM_VERTICES_PER_PARTICLE]( + vec2(-1.0, 0.0), + vec2(-1.0, 1.0), + vec2(0.0, 0.0), + vec2(0.0, 1.0) +); + + void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); @@ -113,28 +122,54 @@ void main(void) { int twoTriID = gl_VertexID - particleID * NUM_VERTICES_PER_PARTICLE; // Particle properties - float age = inColor.x / particle.lifespan.x; + float age = inColor.x / particle.lifespan; float seed = inColor.y; - // Pass the texcoord and the z texcoord is representing the texture icon - // Offset for corrected vertex ordering. - varTexcoord = vec2((UNIT_QUAD[twoTriID].xy -1.0) * vec2(0.5, -0.5)); + // Pass the texcoord + varTexcoord = TEX_COORDS[twoTriID].xy; varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age); vec3 colorSpread = 2.0 * vec3(hifi_hash(seed), hifi_hash(seed * 2.0), hifi_hash(seed * 3.0)) - 1.0; varColor.rgb = clamp(varColor.rgb + colorSpread * particle.color.spread.rgb, vec3(0), vec3(1)); float alphaSpread = 2.0 * hifi_hash(seed * 4.0) - 1.0; varColor.a = clamp(varColor.a + alphaSpread * particle.color.spread.a, 0.0, 1.0); + float spin = interpolate3Points(particle.spin.start, particle.spin.middle, particle.spin.finish, age); + float spinSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0; + spin = spin + spinSpread * particle.spin.spread; + // anchor point in eye space float radius = interpolate3Points(particle.radius.start, particle.radius.middle, particle.radius.finish, age); - float radiusSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0; + float radiusSpread = 2.0 * hifi_hash(seed * 6.0) - 1.0; radius = max(radius + radiusSpread * particle.radius.spread, 0.0); - vec4 quadPos = radius * UNIT_QUAD[twoTriID]; - vec4 anchorPoint; - vec4 _inPosition = vec4(inPosition, 1.0); - <$transformModelToEyePos(cam, obj, _inPosition, anchorPoint)$> + // inPosition is in world space + vec4 anchorPoint = cam._view * vec4(inPosition, 1.0); - vec4 eyePos = anchorPoint + quadPos; + mat3 view3 = mat3(cam._view); + vec3 UP = vec3(0, 1, 0); + vec3 modelUpWorld; + <$transformModelToWorldDir(cam, obj, UP, modelUpWorld)$> + vec3 upWorld = mix(UP, normalize(modelUpWorld), particle.rotateWithEntity); + vec3 upEye = normalize(view3 * upWorld); + vec3 FORWARD = vec3(0, 0, -1); + vec3 particleRight = normalize(cross(FORWARD, upEye)); + vec3 particleUp = cross(particleRight, FORWARD); // don't need to normalize + // This ordering ensures that un-rotated particles render upright in the viewer. + vec3 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec3[NUM_VERTICES_PER_PARTICLE]( + normalize(-particleRight + particleUp), + normalize(-particleRight - particleUp), + normalize(particleRight + particleUp), + normalize(particleRight - particleUp) + ); + float c = cos(spin); + float s = sin(spin); + mat4 rotation = mat4( + c, -s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + vec4 quadPos = radius * vec4(UNIT_QUAD[twoTriID], 0.0); + vec4 eyePos = anchorPoint + rotation * quadPos; <$transformEyeToClipPos(cam, eyePos, gl_Position)$> } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3a11fd821a..81cb18dee4 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -378,8 +378,6 @@ public: /// return preferred shape type (actual physical shape may differ) virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } - virtual void setCollisionShape(const btCollisionShape* shape) {} - void setPosition(const glm::vec3& value); virtual void setParentID(const QUuid& parentID) override; virtual void setShapeType(ShapeType type) { /* do nothing */ } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index efd2376677..2d0fcd0496 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -369,6 +369,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_SPIN, particleSpin); + CHECK_PROPERTY_CHANGE(PROP_SPIN_SPREAD, spinSpread); + CHECK_PROPERTY_CHANGE(PROP_SPIN_START, spinStart); + CHECK_PROPERTY_CHANGE(PROP_SPIN_FINISH, spinFinish); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -908,6 +913,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * alpha value is used. * @property {number} alphaSpread=0 - The spread in alpha that each particle is given. If alpha == 0.5 * and alphaSpread == 0.25, each particle will have an alpha in the range 0.250.75. + * @property {number} particleSpin=0 - The spin of each particle at the middle of its life. In the range -2*PI2*PI. + * @property {number} spinStart=NaN - The spin of each particle at the start of its life. In the range -2*PI2*PI. + * If NaN, the particleSpin value is used. + * @property {number} spinFinish=NaN - The spin of each particle at the end of its life. In the range -2*PI2*PI. + * If NaN, the particleSpin value is used. + * @property {number} spinSpread=0 - The spread in spin that each particle is given. In the range 02*PI. If particleSpin == PI + * and spinSpread == PI/2, each particle will have a spin in the range PI/23*PI/2. + * @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when particleSpin == 0, the particles will point + * up in the world. If true, they will point towards the entity's up vector, based on its orientation. * * @property {ShapeType} shapeType="none" - Currently not used. Read-only. * @@ -1291,6 +1305,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMITTER_SHOULD_TRAIL, emitterShouldTrail); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_SPIN, particleSpin); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_SPREAD, spinSpread); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_START, spinStart); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_FINISH, spinFinish); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity); } // Models only @@ -1583,6 +1602,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData); COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); + COPY_PROPERTY_FROM_QSCRIPTVALUE(particleSpin, float, setParticleSpin); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinSpread, float, setSpinSpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinStart, float, setSpinStart); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinFinish, float, setSpinFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(rotateWithEntity, bool, setRotateWithEntity); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1751,6 +1775,11 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(radiusSpread); COPY_PROPERTY_IF_CHANGED(radiusStart); COPY_PROPERTY_IF_CHANGED(radiusFinish); + COPY_PROPERTY_IF_CHANGED(particleSpin); + COPY_PROPERTY_IF_CHANGED(spinSpread); + COPY_PROPERTY_IF_CHANGED(spinStart); + COPY_PROPERTY_IF_CHANGED(spinFinish); + COPY_PROPERTY_IF_CHANGED(rotateWithEntity); // Certifiable Properties COPY_PROPERTY_IF_CHANGED(itemName); @@ -1964,6 +1993,12 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); + ADD_PROPERTY_TO_MAP(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_START, SpinStart, spinStart, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_FINISH, SpinFinish, spinFinish, float); + ADD_PROPERTY_TO_MAP(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, float); + // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString); @@ -2292,6 +2327,11 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, properties.getAlphaStart()); APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, properties.getAlphaFinish()); APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, properties.getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, properties.getParticleSpin()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, properties.getSpinSpread()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_START, properties.getSpinStart()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, properties.getSpinFinish()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, properties.getRotateWithEntity()) } if (properties.getType() == EntityTypes::Zone) { @@ -2667,6 +2707,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_START, float, setAlphaStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_FINISH, float, setAlphaFinish); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_SPIN, float, setParticleSpin); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_SPREAD, float, setSpinSpread); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_START, float, setSpinStart); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_FINISH, float, setSpinFinish); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity); } if (properties.getType() == EntityTypes::Zone) { @@ -2936,7 +2981,7 @@ void EntityItemProperties::markAllChanged() { _shapeTypeChanged = true; _isEmittingChanged = true; - _emitterShouldTrail = true; + _emitterShouldTrailChanged = true; _maxParticlesChanged = true; _lifespanChanged = true; _emitRateChanged = true; @@ -2961,6 +3006,11 @@ void EntityItemProperties::markAllChanged() { _colorFinishChanged = true; _alphaStartChanged = true; _alphaFinishChanged = true; + _particleSpinChanged = true; + _spinStartChanged = true; + _spinFinishChanged = true; + _spinSpreadChanged = true; + _rotateWithEntityChanged = true; _materialURLChanged = true; _materialMappingModeChanged = true; @@ -3309,6 +3359,21 @@ QList EntityItemProperties::listChangedProperties() { if (radiusFinishChanged()) { out += "radiusFinish"; } + if (particleSpinChanged()) { + out += "particleSpin"; + } + if (spinSpreadChanged()) { + out += "spinSpread"; + } + if (spinStartChanged()) { + out += "spinStart"; + } + if (spinFinishChanged()) { + out += "spinFinish"; + } + if (rotateWithEntityChanged()) { + out += "rotateWithEntity"; + } if (materialURLChanged()) { out += "materialURL"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 93b8c991d5..04e54c54a5 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -235,6 +235,12 @@ public: DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); + DEFINE_PROPERTY(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float, particle::DEFAULT_PARTICLE_SPIN); + DEFINE_PROPERTY(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float, particle::DEFAULT_SPIN_SPREAD); + DEFINE_PROPERTY(PROP_SPIN_START, SpinStart, spinStart, float, particle::DEFAULT_SPIN_START); + DEFINE_PROPERTY(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, particle::DEFAULT_SPIN_FINISH); + DEFINE_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, bool, particle::DEFAULT_ROTATE_WITH_ENTITY); + // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d43a991f22..156c5d9dd4 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -251,6 +251,12 @@ enum EntityPropertyList { PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire, only used locally + PROP_PARTICLE_SPIN, + PROP_SPIN_START, + PROP_SPIN_FINISH, + PROP_SPIN_SPREAD, + PROP_PARTICLE_ROTATE_WITH_ENTITY, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 18cf7c9252..238f41b05f 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -91,6 +91,8 @@ bool operator==(const Properties& a, const Properties& b) { (a.color == b.color) && (a.alpha == b.alpha) && (a.radius == b.radius) && + (a.spin == b.spin) && + (a.rotateWithEntity == b.rotateWithEntity) && (a.radiusStart == b.radiusStart) && (a.lifespan == b.lifespan) && (a.maxParticles == b.maxParticles) && @@ -130,7 +132,11 @@ bool Properties::valid() const { (radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && - (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)); + (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && + (spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)); } bool Properties::emitting() const { @@ -332,6 +338,43 @@ void ParticleEffectEntityItem::setRadiusSpread(float radiusSpread) { } } +void ParticleEffectEntityItem::setParticleSpin(float particleSpin) { + particleSpin = glm::clamp(particleSpin, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (particleSpin != _particleProperties.spin.gradient.target) { + withWriteLock([&] { + _particleProperties.spin.gradient.target = particleSpin; + }); + } +} + +void ParticleEffectEntityItem::setSpinStart(float spinStart) { + spinStart = + glm::isnan(spinStart) ? spinStart : glm::clamp(spinStart, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinStart != _particleProperties.spin.range.start) { + withWriteLock([&] { + _particleProperties.spin.range.start = spinStart; + }); + } +} + +void ParticleEffectEntityItem::setSpinFinish(float spinFinish) { + spinFinish = + glm::isnan(spinFinish) ? spinFinish : glm::clamp(spinFinish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinFinish != _particleProperties.spin.range.finish) { + withWriteLock([&] { + _particleProperties.spin.range.finish = spinFinish; + }); + } +} + +void ParticleEffectEntityItem::setSpinSpread(float spinSpread) { + spinSpread = glm::clamp(spinSpread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinSpread != _particleProperties.spin.gradient.spread) { + withWriteLock([&] { + _particleProperties.spin.gradient.spread = spinSpread; + }); + } +} void ParticleEffectEntityItem::computeAndUpdateDimensions() { particle::Properties particleProperties; @@ -398,6 +441,11 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(EntityPropertyFlags COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaFinish, getAlphaFinish); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitterShouldTrail, getEmitterShouldTrail); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleSpin, getParticleSpin); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinSpread, getSpinSpread); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinStart, getSpinStart); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinFinish, getSpinFinish); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotateWithEntity, getRotateWithEntity); return properties; } @@ -435,6 +483,11 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaFinish, setAlphaFinish); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitterShouldTrail, setEmitterShouldTrail); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleSpin, setParticleSpin); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinSpread, setSpinSpread); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinStart, setSpinStart); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinFinish, setSpinFinish); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotateWithEntity, setRotateWithEntity); if (somethingChanged) { bool wantDebug = false; @@ -515,6 +568,12 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, float, setParticleSpin); + READ_ENTITY_PROPERTY(PROP_SPIN_SPREAD, float, setSpinSpread); + READ_ENTITY_PROPERTY(PROP_SPIN_START, float, setSpinStart); + READ_ENTITY_PROPERTY(PROP_SPIN_FINISH, float, setSpinFinish); + READ_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity); + return bytesRead; } @@ -551,6 +610,11 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea requestedProperties += PROP_AZIMUTH_START; requestedProperties += PROP_AZIMUTH_FINISH; requestedProperties += PROP_EMITTER_SHOULD_TRAIL; + requestedProperties += PROP_PARTICLE_SPIN; + requestedProperties += PROP_SPIN_SPREAD; + requestedProperties += PROP_SPIN_START; + requestedProperties += PROP_SPIN_FINISH; + requestedProperties += PROP_PARTICLE_ROTATE_WITH_ENTITY; return requestedProperties; } @@ -594,6 +658,11 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, getAzimuthStart()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, getAzimuthFinish()); APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, getParticleSpin()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, getSpinSpread()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_START, getSpinStart()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, getSpinFinish()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, getRotateWithEntity()); } @@ -665,6 +734,12 @@ void ParticleEffectEntityItem::setEmitterShouldTrail(bool emitterShouldTrail) { }); } +void ParticleEffectEntityItem::setRotateWithEntity(bool rotateWithEntity) { + withWriteLock([&] { + _particleProperties.rotateWithEntity = rotateWithEntity; + }); +} + particle::Properties ParticleEffectEntityItem::getParticleProperties() const { particle::Properties result; withReadLock([&] { @@ -689,6 +764,12 @@ particle::Properties ParticleEffectEntityItem::getParticleProperties() const { if (glm::isnan(result.radius.range.finish)) { result.radius.range.finish = getParticleRadius(); } + if (glm::isnan(result.spin.range.start)) { + result.spin.range.start = getParticleSpin(); + } + if (glm::isnan(result.spin.range.finish)) { + result.spin.range.finish = getParticleSpin(); + } }); if (!result.valid()) { diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 7e507ab46a..480b78ffcc 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -39,7 +39,7 @@ namespace particle { static const float MINIMUM_EMIT_RATE = 0.0f; static const float MAXIMUM_EMIT_RATE = 100000.0f; static const float DEFAULT_EMIT_SPEED = 5.0f; - static const float MINIMUM_EMIT_SPEED = 0.0f; + static const float MINIMUM_EMIT_SPEED = -1000.0f; static const float MAXIMUM_EMIT_SPEED = 1000.0f; // Approx mach 3 static const float DEFAULT_SPEED_SPREAD = 1.0f; static const glm::quat DEFAULT_EMIT_ORIENTATION = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_X); // Vertical @@ -69,8 +69,15 @@ namespace particle { static const float DEFAULT_RADIUS_SPREAD = 0.0f; static const float DEFAULT_RADIUS_START = UNINITIALIZED; static const float DEFAULT_RADIUS_FINISH = UNINITIALIZED; + static const float DEFAULT_PARTICLE_SPIN = 0.0f; + static const float DEFAULT_SPIN_START = UNINITIALIZED; + static const float DEFAULT_SPIN_FINISH = UNINITIALIZED; + static const float DEFAULT_SPIN_SPREAD = 0.0f; + static const float MINIMUM_PARTICLE_SPIN = -2.0f * SCRIPT_MAXIMUM_PI; + static const float MAXIMUM_PARTICLE_SPIN = 2.0f * SCRIPT_MAXIMUM_PI; static const QString DEFAULT_TEXTURES = ""; static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false; + static const bool DEFAULT_ROTATE_WITH_ENTITY = false; template struct Range { @@ -151,6 +158,8 @@ namespace particle { RangeGradient alpha { DEFAULT_ALPHA, DEFAULT_ALPHA_START, DEFAULT_ALPHA_FINISH, DEFAULT_ALPHA_SPREAD }; float radiusStart { DEFAULT_EMIT_RADIUS_START }; RangeGradient radius { DEFAULT_PARTICLE_RADIUS, DEFAULT_RADIUS_START, DEFAULT_RADIUS_FINISH, DEFAULT_RADIUS_SPREAD }; + RangeGradient spin { DEFAULT_PARTICLE_SPIN, DEFAULT_SPIN_START, DEFAULT_SPIN_FINISH, DEFAULT_SPIN_SPREAD }; + bool rotateWithEntity { DEFAULT_ROTATE_WITH_ENTITY }; float lifespan { DEFAULT_LIFESPAN }; uint32_t maxParticles { DEFAULT_MAX_PARTICLES }; EmitProperties emission; @@ -168,6 +177,8 @@ namespace particle { Properties& operator =(const Properties& other) { color = other.color; alpha = other.alpha; + spin = other.spin; + rotateWithEntity = other.rotateWithEntity; radius = other.radius; lifespan = other.lifespan; maxParticles = other.maxParticles; @@ -306,6 +317,21 @@ public: void setRadiusSpread(float radiusSpread); float getRadiusSpread() const { return _particleProperties.radius.gradient.spread; } + void setParticleSpin(float particleSpin); + float getParticleSpin() const { return _particleProperties.spin.gradient.target; } + + void setSpinStart(float spinStart); + float getSpinStart() const { return _particleProperties.spin.range.start; } + + void setSpinFinish(float spinFinish); + float getSpinFinish() const { return _particleProperties.spin.range.finish; } + + void setSpinSpread(float spinSpread); + float getSpinSpread() const { return _particleProperties.spin.gradient.spread; } + + void setRotateWithEntity(bool rotateWithEntity); + bool getRotateWithEntity() const { return _particleProperties.rotateWithEntity; } + void computeAndUpdateDimensions(); void setTextures(const QString& textures); diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 50c0bc13ed..864a106350 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -238,7 +238,7 @@ TransformObject getTransformObject() { <@endfunc@> <@func transformModelToWorldDir(cameraTransform, objectTransform, modelDir, worldDir)@> - { // transformModelToEyeDir + { // transformModelToWorldDir vec3 mr0 = <$objectTransform$>._modelInverse[0].xyz; vec3 mr1 = <$objectTransform$>._modelInverse[1].xyz; vec3 mr2 = <$objectTransform$>._modelInverse[2].xyz; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 13ffcb5120..d0bd7fc872 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -33,7 +33,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::ParticleEntityFix); + return static_cast(EntityVersion::ParticleSpin); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 6e1aca83e5..2ffadfef4b 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -238,7 +238,8 @@ enum class EntityVersion : PacketVersion { CloneableData, CollisionMask16Bytes, YieldSimulationOwnership, - ParticleEntityFix + ParticleEntityFix, + ParticleSpin }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/physics/src/CollisionRenderMeshCache.cpp b/libraries/physics/src/CollisionRenderMeshCache.cpp deleted file mode 100644 index 6f66b9af10..0000000000 --- a/libraries/physics/src/CollisionRenderMeshCache.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// -// CollisionRenderMeshCache.cpp -// libraries/physics/src -// -// Created by Andrew Meadows 2016.07.13 -// Copyright 2016 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 "CollisionRenderMeshCache.h" - -#include - -#include -#include - -#include // for MAX_HULL_POINTS - -const int32_t MAX_HULL_INDICES = 6 * MAX_HULL_POINTS; -const int32_t MAX_HULL_NORMALS = MAX_HULL_INDICES; -float tempVertices[MAX_HULL_NORMALS]; -graphics::Index tempIndexBuffer[MAX_HULL_INDICES]; - -bool copyShapeToMesh(const btTransform& transform, const btConvexShape* shape, - gpu::BufferView& vertices, gpu::BufferView& indices, gpu::BufferView& parts, - gpu::BufferView& normals) { - assert(shape); - - btShapeHull hull(shape); - if (!hull.buildHull(shape->getMargin())) { - return false; - } - - int32_t numHullIndices = hull.numIndices(); - assert(numHullIndices <= MAX_HULL_INDICES); - - int32_t numHullVertices = hull.numVertices(); - assert(numHullVertices <= MAX_HULL_POINTS); - - { // new part - graphics::Mesh::Part part; - part._startIndex = (graphics::Index)indices.getNumElements(); - part._numIndices = (graphics::Index)numHullIndices; - // FIXME: the render code cannot handle the case where part._baseVertex != 0 - //part._baseVertex = vertices.getNumElements(); // DOES NOT WORK - part._baseVertex = 0; - - gpu::BufferView::Size numBytes = sizeof(graphics::Mesh::Part); - const gpu::Byte* data = reinterpret_cast(&part); - parts._buffer->append(numBytes, data); - parts._size = parts._buffer->getSize(); - } - - const int32_t SIZE_OF_VEC3 = 3 * sizeof(float); - graphics::Index indexOffset = (graphics::Index)vertices.getNumElements(); - - { // new indices - const uint32_t* hullIndices = hull.getIndexPointer(); - // FIXME: the render code cannot handle the case where part._baseVertex != 0 - // so we must add an offset to each index - for (int32_t i = 0; i < numHullIndices; ++i) { - tempIndexBuffer[i] = hullIndices[i] + indexOffset; - } - const gpu::Byte* data = reinterpret_cast(tempIndexBuffer); - gpu::BufferView::Size numBytes = (gpu::BufferView::Size)(sizeof(graphics::Index) * numHullIndices); - indices._buffer->append(numBytes, data); - indices._size = indices._buffer->getSize(); - } - { // new vertices - const btVector3* hullVertices = hull.getVertexPointer(); - assert(numHullVertices <= MAX_HULL_POINTS); - for (int32_t i = 0; i < numHullVertices; ++i) { - btVector3 transformedPoint = transform * hullVertices[i]; - memcpy(tempVertices + 3 * i, transformedPoint.m_floats, SIZE_OF_VEC3); - } - gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices); - const gpu::Byte* data = reinterpret_cast(tempVertices); - vertices._buffer->append(numBytes, data); - vertices._size = vertices._buffer->getSize(); - } - { // new normals - // compute average point - btVector3 avgVertex(0.0f, 0.0f, 0.0f); - const btVector3* hullVertices = hull.getVertexPointer(); - for (int i = 0; i < numHullVertices; ++i) { - avgVertex += hullVertices[i]; - } - avgVertex = transform * (avgVertex * (1.0f / (float)numHullVertices)); - - for (int i = 0; i < numHullVertices; ++i) { - btVector3 norm = transform * hullVertices[i] - avgVertex; - btScalar normLength = norm.length(); - if (normLength > FLT_EPSILON) { - norm /= normLength; - } - memcpy(tempVertices + 3 * i, norm.m_floats, SIZE_OF_VEC3); - } - gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices); - const gpu::Byte* data = reinterpret_cast(tempVertices); - normals._buffer->append(numBytes, data); - normals._size = vertices._buffer->getSize(); - } - return true; -} - -graphics::MeshPointer createMeshFromShape(const void* pointer) { - graphics::MeshPointer mesh; - if (!pointer) { - return mesh; - } - - // pointer must be a const btCollisionShape* (cast to void*), but it only - // needs to be valid here when its render mesh is created, after this call - // the cache doesn't care what happens to the shape behind the pointer - const btCollisionShape* shape = static_cast(pointer); - - int32_t shapeType = shape->getShapeType(); - if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE || shape->isConvex()) { - // allocate buffers for it - gpu::BufferView vertices(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - gpu::BufferView indices(new gpu::Buffer(), gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::INDEX)); - gpu::BufferView parts(new gpu::Buffer(), gpu::Element(gpu::VEC4, gpu::UINT32, gpu::PART)); - gpu::BufferView normals(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - - int32_t numSuccesses = 0; - if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE) { - const btCompoundShape* compoundShape = static_cast(shape); - int32_t numSubShapes = compoundShape->getNumChildShapes(); - for (int32_t i = 0; i < numSubShapes; ++i) { - const btCollisionShape* childShape = compoundShape->getChildShape(i); - if (childShape->isConvex()) { - const btConvexShape* convexShape = static_cast(childShape); - if (copyShapeToMesh(compoundShape->getChildTransform(i), convexShape, vertices, indices, parts, normals)) { - numSuccesses++; - } - } - } - } else { - // shape is convex - const btConvexShape* convexShape = static_cast(shape); - btTransform transform; - transform.setIdentity(); - if (copyShapeToMesh(transform, convexShape, vertices, indices, parts, normals)) { - numSuccesses++; - } - } - if (numSuccesses > 0) { - mesh = std::make_shared(); - mesh->setVertexBuffer(vertices); - mesh->setIndexBuffer(indices); - mesh->setPartBuffer(parts); - mesh->addAttribute(gpu::Stream::NORMAL, normals); - } else { - // TODO: log failure message here - } - } - return mesh; -} - -CollisionRenderMeshCache::CollisionRenderMeshCache() { -} - -CollisionRenderMeshCache::~CollisionRenderMeshCache() { - _meshMap.clear(); - _pendingGarbage.clear(); -} - -graphics::MeshPointer CollisionRenderMeshCache::getMesh(CollisionRenderMeshCache::Key key) { - graphics::MeshPointer mesh; - if (key) { - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr == _meshMap.end()) { - // make mesh and add it to map - mesh = createMeshFromShape(key); - if (mesh) { - _meshMap.insert(std::make_pair(key, mesh)); - } - } else { - mesh = itr->second; - } - } - const uint32_t MAX_NUM_PENDING_GARBAGE = 20; - if (_pendingGarbage.size() > MAX_NUM_PENDING_GARBAGE) { - collectGarbage(); - } - return mesh; -} - -bool CollisionRenderMeshCache::releaseMesh(CollisionRenderMeshCache::Key key) { - if (!key) { - return false; - } - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr != _meshMap.end()) { - _pendingGarbage.push_back(key); - return true; - } - return false; -} - -void CollisionRenderMeshCache::collectGarbage() { - uint32_t numShapes = (uint32_t)_pendingGarbage.size(); - for (uint32_t i = 0; i < numShapes; ++i) { - CollisionRenderMeshCache::Key key = _pendingGarbage[i]; - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr != _meshMap.end()) { - if ((*itr).second.use_count() == 1) { - // we hold the only reference - _meshMap.erase(itr); - } - } - } - _pendingGarbage.clear(); -} - diff --git a/libraries/physics/src/CollisionRenderMeshCache.h b/libraries/physics/src/CollisionRenderMeshCache.h deleted file mode 100644 index c5b643c0cc..0000000000 --- a/libraries/physics/src/CollisionRenderMeshCache.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// CollisionRenderMeshCache.h -// libraries/physics/src -// -// Created by Andrew Meadows 2016.07.13 -// Copyright 2016 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_CollisionRenderMeshCache_h -#define hifi_CollisionRenderMeshCache_h - -#include -#include -#include - -#include - - -class CollisionRenderMeshCache { -public: - using Key = const void*; // must actually be a const btCollisionShape* - - CollisionRenderMeshCache(); - ~CollisionRenderMeshCache(); - - /// \return pointer to geometry - graphics::MeshPointer getMesh(Key key); - - /// \return true if geometry was found and released - bool releaseMesh(Key key); - - /// delete geometries that have zero references - void collectGarbage(); - - // validation methods - uint32_t getNumMeshes() const { return (uint32_t)_meshMap.size(); } - bool hasMesh(Key key) const { return _meshMap.find(key) == _meshMap.end(); } - -private: - using CollisionMeshMap = std::unordered_map; - CollisionMeshMap _meshMap; - std::vector _pendingGarbage; -}; - -#endif // hifi_CollisionRenderMeshCache_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index a610a6f2a6..925cfee740 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -307,13 +307,6 @@ const btCollisionShape* EntityMotionState::computeNewShape() { return getShapeManager()->getShape(shapeInfo); } -void EntityMotionState::setShape(const btCollisionShape* shape) { - if (_shape != shape) { - ObjectMotionState::setShape(shape); - _entity->setCollisionShape(_shape); - } -} - bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // NOTE: this method is only ever called when the entity simulation is locally owned DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index b215537d55..653e3f4252 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -118,7 +118,6 @@ protected: bool isReadyToComputeShape() const override; const btCollisionShape* computeNewShape() override; - void setShape(const btCollisionShape* shape) override; void setMotionType(PhysicsMotionType motionType) override; // EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 0e6eb2511d..310cf7cec1 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -155,26 +155,25 @@ void ObjectMotionState::setMotionType(PhysicsMotionType motionType) { // Update the Continuous Collision Detection (CCD) configuration settings of our RigidBody so that // CCD will be enabled automatically when its speed surpasses a certain threshold. void ObjectMotionState::updateCCDConfiguration() { - if (_body) { - if (_shape) { - // If this object moves faster than its bounding radius * RADIUS_MOTION_THRESHOLD_MULTIPLIER, - // CCD will be enabled for this object. - const auto RADIUS_MOTION_THRESHOLD_MULTIPLIER = 0.5f; + assert(_body); + if (_shape && _shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) { + // find minumum dimension of shape + btVector3 aabbMin, aabbMax; + btTransform transform; + transform.setIdentity(); + _shape->getAabb(transform, aabbMin, aabbMax); + aabbMin = aabbMax - aabbMin; + btScalar radius = *((btScalar*)(aabbMin) + aabbMin.minAxis()); - btVector3 center; - btScalar radius; - _shape->getBoundingSphere(center, radius); - _body->setCcdMotionThreshold(radius * RADIUS_MOTION_THRESHOLD_MULTIPLIER); + // use the minimum dimension as the radius of the CCD proxy sphere + _body->setCcdSweptSphereRadius(radius); - // TODO: Ideally the swept sphere radius would be contained by the object. Using the bounding sphere - // radius works well for spherical objects, but may cause issues with other shapes. For arbitrary - // objects we may want to consider a different approach, such as grouping rigid bodies together. - - _body->setCcdSweptSphereRadius(radius); - } else { - // Disable CCD - _body->setCcdMotionThreshold(0); - } + // also use the radius as the motion threshold for enabling CCD + _body->setCcdMotionThreshold(radius); + } else { + // disable CCD + _body->setCcdSweptSphereRadius(0.0f); + _body->setCcdMotionThreshold(0.0f); } } @@ -188,8 +187,8 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { if (_body) { _body->setUserPointer(this); assert(_body->getCollisionShape() == _shape); + updateCCDConfiguration(); } - updateCCDConfiguration(); } } @@ -199,6 +198,9 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) { getShapeManager()->releaseShape(_shape); } _shape = shape; + if (_body) { + updateCCDConfiguration(); + } } } @@ -312,7 +314,6 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* } else { _body->setCollisionShape(const_cast(newShape)); setShape(newShape); - updateCCDConfiguration(); } } if (flags & EASY_DIRTY_PHYSICS_FLAGS) { diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 55895e31a4..cc2edfcca7 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -59,7 +59,7 @@ void RecordingScriptingInterface::playClip(NetworkClipLoaderPointer clipLoader, if (callback.isFunction()) { QScriptValueList args { true, url }; - callback.call(_scriptEngine->globalObject(), args); + callback.call(QScriptValue(), args); } } @@ -78,7 +78,7 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue auto weakClipLoader = clipLoader.toWeakRef(); // when clip loaded, call the callback with the URL and success boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, this, + connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, callback.engine(), [this, weakClipLoader, url, callback]() mutable { if (auto clipLoader = weakClipLoader.toStrongRef()) { @@ -92,12 +92,12 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue }); // when clip load fails, call the callback with the URL and failure boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::failed, this, [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { + connect(clipLoader.data(), &recording::NetworkClipLoader::failed, callback.engine(), [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { qCDebug(scriptengine) << "Failed to load recording from" << url; if (callback.isFunction()) { QScriptValueList args { false, url }; - callback.call(_scriptEngine->currentContext()->thisObject(), args); + callback.call(QScriptValue(), args); } if (auto clipLoader = weakClipLoader.toStrongRef()) { diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 29d9b31049..c4d576351f 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -36,8 +36,6 @@ class RecordingScriptingInterface : public QObject, public Dependency { public: RecordingScriptingInterface(); - void setScriptEngine(QSharedPointer scriptEngine) { _scriptEngine = scriptEngine; } - public slots: /**jsdoc @@ -246,7 +244,6 @@ protected: Flag _useSkeletonModel { false }; recording::ClipPointer _lastClip; - QSharedPointer _scriptEngine; QSet _clipLoaders; private: diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 3dc344dc6f..d9b26927e2 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -23,10 +23,10 @@ const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_E const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; -const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.10f; +const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.12f; const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.10f; const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.04f; -const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.07f; +const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.05f; const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.3f; const float DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD = -0.02f; const float DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD = 0.4f; diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 3bf99ca26a..6564c91d8a 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -325,7 +325,7 @@ Script.include("/~/system/libraries/controllers.js"); } else if (target === TARGET.SURFACE) { var offset = getAvatarFootOffset(); result.intersection.y += offset; - MyAvatar.goToLocation(result.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); + MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false); HMD.centerUI(); MyAvatar.centerBody(); } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 1c0b7c2dcb..4b8abff84b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -392,6 +392,8 @@ var toolBar = (function () { entityListTool.sendUpdate(); selectionManager.setSelections([entityID]); + Window.setFocus(); + return entityID; } diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index cb2c2f3374..f1b7c8600f 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -361,6 +361,55 @@ type: "Row" } ], + Spin: [ + { + id: "particleSpin", + name: "Particle Spin", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinSpread", + name: "Spin Spread", + type: "SliderRadian", + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinStart", + name: "Spin Start", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinFinish", + name: "Spin Finish", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "rotateWithEntity", + name: "Rotate with Entity", + type: "Boolean" + }, + { + type: "Row" + } + ], Polar: [ { id: "polarStart", diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js index 1914180ff9..a3be004329 100644 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ b/scripts/system/particle_explorer/particleExplorerTool.js @@ -75,6 +75,12 @@ ParticleExplorerTool = function(createToolsWindow) { if (isNaN(properties.colorFinish.red)) { properties.colorFinish = properties.color; } + if (isNaN(properties.spinStart)) { + properties.spinStart = properties.particleSpin; + } + if (isNaN(properties.spinFinish)) { + properties.spinFinish = properties.particleSpin; + } sendParticleProperties(properties); } @@ -88,8 +94,8 @@ ParticleExplorerTool = function(createToolsWindow) { if (data.messageType === "settings_update") { var updatedSettings = data.updatedSettings; - var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish"]; - var fallbackProps = ["alpha", "particleRadius", "color"]; + var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish", "spinStart", "spinFinish"]; + var fallbackProps = ["alpha", "particleRadius", "color", "particleSpin"]; for (var i = 0; i < optionalProps.length; i++) { var fallbackProp = fallbackProps[Math.floor(i / 2)]; var optionalValue = updatedSettings[optionalProps[i]]; diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.cpp b/tests/physics/src/CollisionRenderMeshCacheTests.cpp deleted file mode 100644 index da28598dda..0000000000 --- a/tests/physics/src/CollisionRenderMeshCacheTests.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// -// CollisionRenderMeshCacheTests.cpp -// tests/physics/src -// -// Created by Andrew Meadows on 2014.10.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 -// - -#include "CollisionRenderMeshCacheTests.h" - -#include -#include - -#include -#include - -#include -#include // for MAX_HULL_POINTS - -#include "MeshUtil.h" - - -QTEST_MAIN(CollisionRenderMeshCacheTests) - -const float INV_SQRT_THREE = 0.577350269f; - -const uint32_t numSphereDirections = 6 + 8; -btVector3 sphereDirections[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(-1.0f, 0.0f, 0.0f), - btVector3(0.0f, 1.0f, 0.0f), - btVector3(0.0f, -1.0f, 0.0f), - btVector3(0.0f, 0.0f, 1.0f), - btVector3(0.0f, 0.0f, -1.0f), - btVector3(INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE) -}; - -float randomFloat() { - return 2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f; -} - -btBoxShape* createBoxShape(const btVector3& extent) { - btBoxShape* shape = new btBoxShape(0.5f * extent); - return shape; -} - -btConvexHullShape* createConvexHull(float radius) { - btConvexHullShape* hull = new btConvexHullShape(); - for (uint32_t i = 0; i < numSphereDirections; ++i) { - btVector3 point = radius * sphereDirections[i]; - hull->addPoint(point, false); - } - hull->recalcLocalAabb(); - return hull; -} - -void CollisionRenderMeshCacheTests::testShapeHullManifold() { - // make a box shape - btVector3 extent(1.0f, 2.0f, 3.0f); - btBoxShape* box = createBoxShape(extent); - - // wrap it with a ShapeHull - btShapeHull hull(box); - const float MARGIN = 0.0f; - hull.buildHull(MARGIN); - - // verify the vertex count is capped - uint32_t numVertices = (uint32_t)hull.numVertices(); - QVERIFY(numVertices <= MAX_HULL_POINTS); - - // verify the mesh is inside the radius - btVector3 halfExtents = box->getHalfExtentsWithMargin(); - float ACCEPTABLE_EXTENTS_ERROR = 0.01f; - float maxRadius = halfExtents.length() + ACCEPTABLE_EXTENTS_ERROR; - const btVector3* meshVertices = hull.getVertexPointer(); - for (uint32_t i = 0; i < numVertices; ++i) { - btVector3 vertex = meshVertices[i]; - QVERIFY(vertex.length() <= maxRadius); - } - - // verify the index count is capped - uint32_t numIndices = (uint32_t)hull.numIndices(); - QVERIFY(numIndices < 6 * MAX_HULL_POINTS); - - // verify the index count is a multiple of 3 - QVERIFY(numIndices % 3 == 0); - - // verify the mesh is closed - const uint32_t* meshIndices = hull.getIndexPointer(); - bool isClosed = MeshUtil::isClosedManifold(meshIndices, numIndices); - QVERIFY(isClosed); - - // verify the triangle normals are outward using right-hand-rule - const uint32_t INDICES_PER_TRIANGLE = 3; - for (uint32_t i = 0; i < numIndices; i += INDICES_PER_TRIANGLE) { - btVector3 A = meshVertices[meshIndices[i]]; - btVector3 B = meshVertices[meshIndices[i+1]]; - btVector3 C = meshVertices[meshIndices[i+2]]; - - btVector3 face = (B - A).cross(C - B); - btVector3 center = (A + B + C) / 3.0f; - QVERIFY(face.dot(center) > 0.0f); - } - - // delete unmanaged memory - delete box; -} - -void CollisionRenderMeshCacheTests::testCompoundShape() { - uint32_t numSubShapes = 3; - - btVector3 centers[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(0.0f, -2.0f, 0.0f), - btVector3(0.0f, 0.0f, 3.0f), - }; - - float radii[] = { 3.0f, 2.0f, 1.0f }; - - btCompoundShape* compoundShape = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btConvexHullShape* hull = createConvexHull(radii[i]); - compoundShape->addChildShape(transform, hull); - } - - // create the cache - CollisionRenderMeshCache cache; - QVERIFY(cache.getNumMeshes() == 0); - - // get the mesh once - graphics::MeshPointer mesh = cache.getMesh(compoundShape); - QVERIFY((bool)mesh); - QVERIFY(cache.getNumMeshes() == 1); - - // get the mesh again - graphics::MeshPointer mesh2 = cache.getMesh(compoundShape); - QVERIFY(mesh2 == mesh); - QVERIFY(cache.getNumMeshes() == 1); - - // forget the mesh once - cache.releaseMesh(compoundShape); - mesh.reset(); - QVERIFY(cache.getNumMeshes() == 1); - - // collect garbage (should still cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 1); - - // forget the mesh a second time (should still cache mesh) - cache.releaseMesh(compoundShape); - mesh2.reset(); - QVERIFY(cache.getNumMeshes() == 1); - - // collect garbage (should no longer cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 0); - - // delete unmanaged memory - for (int i = 0; i < compoundShape->getNumChildShapes(); ++i) { - delete compoundShape->getChildShape(i); - } - delete compoundShape; -} - -void CollisionRenderMeshCacheTests::testMultipleShapes() { - // shapeA is compound of hulls - uint32_t numSubShapes = 3; - btVector3 centers[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(0.0f, -2.0f, 0.0f), - btVector3(0.0f, 0.0f, 3.0f), - }; - float radii[] = { 3.0f, 2.0f, 1.0f }; - btCompoundShape* shapeA = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btConvexHullShape* hull = createConvexHull(radii[i]); - shapeA->addChildShape(transform, hull); - } - - // shapeB is compound of boxes - btVector3 extents[] = { - btVector3(1.0f, 2.0f, 3.0f), - btVector3(2.0f, 3.0f, 1.0f), - btVector3(3.0f, 1.0f, 2.0f), - }; - btCompoundShape* shapeB = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btBoxShape* box = createBoxShape(extents[i]); - shapeB->addChildShape(transform, box); - } - - // shapeC is just a box - btVector3 extentC(7.0f, 3.0f, 5.0f); - btBoxShape* shapeC = createBoxShape(extentC); - - // create the cache - CollisionRenderMeshCache cache; - QVERIFY(cache.getNumMeshes() == 0); - - // get the meshes - graphics::MeshPointer meshA = cache.getMesh(shapeA); - graphics::MeshPointer meshB = cache.getMesh(shapeB); - graphics::MeshPointer meshC = cache.getMesh(shapeC); - QVERIFY((bool)meshA); - QVERIFY((bool)meshB); - QVERIFY((bool)meshC); - QVERIFY(cache.getNumMeshes() == 3); - - // get the meshes again - graphics::MeshPointer meshA2 = cache.getMesh(shapeA); - graphics::MeshPointer meshB2 = cache.getMesh(shapeB); - graphics::MeshPointer meshC2 = cache.getMesh(shapeC); - QVERIFY(meshA == meshA2); - QVERIFY(meshB == meshB2); - QVERIFY(meshC == meshC2); - QVERIFY(cache.getNumMeshes() == 3); - - // forget the meshes once - cache.releaseMesh(shapeA); - cache.releaseMesh(shapeB); - cache.releaseMesh(shapeC); - meshA2.reset(); - meshB2.reset(); - meshC2.reset(); - QVERIFY(cache.getNumMeshes() == 3); - - // collect garbage (should still cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 3); - - // forget again, one mesh at a time... - // shapeA... - cache.releaseMesh(shapeA); - meshA.reset(); - QVERIFY(cache.getNumMeshes() == 3); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 2); - // shapeB... - cache.releaseMesh(shapeB); - meshB.reset(); - QVERIFY(cache.getNumMeshes() == 2); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 1); - // shapeC... - cache.releaseMesh(shapeC); - meshC.reset(); - QVERIFY(cache.getNumMeshes() == 1); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 0); - - // delete unmanaged memory - for (int i = 0; i < shapeA->getNumChildShapes(); ++i) { - delete shapeA->getChildShape(i); - } - delete shapeA; - for (int i = 0; i < shapeB->getNumChildShapes(); ++i) { - delete shapeB->getChildShape(i); - } - delete shapeB; - delete shapeC; -} diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.h b/tests/physics/src/CollisionRenderMeshCacheTests.h deleted file mode 100644 index 640314a2a0..0000000000 --- a/tests/physics/src/CollisionRenderMeshCacheTests.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// CollisionRenderMeshCacheTests.h -// tests/physics/src -// -// Created by Andrew Meadows on 2014.10.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_CollisionRenderMeshCacheTests_h -#define hifi_CollisionRenderMeshCacheTests_h - -#include - -class CollisionRenderMeshCacheTests : public QObject { - Q_OBJECT - -private slots: - void testShapeHullManifold(); - void testCompoundShape(); - void testMultipleShapes(); -}; - -#endif // hifi_CollisionRenderMeshCacheTests_h