diff --git a/examples/example/ui/MyEnergyBar.js b/examples/example/ui/MyEnergyBar.js new file mode 100644 index 0000000000..2d01a66317 --- /dev/null +++ b/examples/example/ui/MyEnergyBar.js @@ -0,0 +1,63 @@ +Script.include("../../libraries/utils.js"); +var energyColor = {red: 0, green: 200, blue: 0}; +var lowEnergyColor = {red: 255, green: 0, blue: 0}; +var totalWidth = 200; +var paddingRight = 50; +var xPosition = Window.innerWidth - totalWidth - paddingRight; +var lowEnergyThreshold = 0.3; +var currentEnergy = 1.0; +var energyLossRate = 0.003; +var energyChargeRate = 0.003; +var isGrabbing = false; +var refractoryPeriod = 2000; + +var lastAvatarVelocity = MyAvatar.getVelocity(); +var lastAvatarPosition = MyAvatar.position; + +var background = Overlays.addOverlay("text", { + x: xPosition, + y: 20, + width: totalWidth, + height: 10, + backgroundColor: {red: 255, green: 0, blue: 0} +}) + +var bar = Overlays.addOverlay("text", { + x: xPosition, + y: 20, + width: totalWidth, + height: 10, + backgroundColor: energyColor +}); + + +// Takes an energy value between 0 and 1 and sets energy bar width appropriately +function setEnergy(energy) { + energy = clamp(energy, 0, 1); + var barWidth = totalWidth * energy; + var color = energy <= lowEnergyThreshold ? lowEnergyColor: energyColor; + Overlays.editOverlay(bar, { width: barWidth, backgroundColor: color}); +} + +function update() { + currentEnergy = clamp(MyAvatar.energy, 0, 1); + setEnergy(currentEnergy); +} + +function cleanup() { + Overlays.deleteOverlay(background); + Overlays.deleteOverlay(bar); +} + +function energyChanged(newValue) { + Entities.currentAvatarEnergy = newValue; +} + +function debitAvatarEnergy(value) { + MyAvatar.energy = MyAvatar.energy - value; +} +Entities.costMultiplier = 0.02; +Entities.debitEnergySource.connect(debitAvatarEnergy); +MyAvatar.energyChanged.connect(energyChanged); +Script.update.connect(update); +Script.scriptEnding.connect(cleanup); diff --git a/examples/example/ui/energyBar.js b/examples/example/ui/energyBar.js index a45b09f6d4..498eef2751 100644 --- a/examples/example/ui/energyBar.js +++ b/examples/example/ui/energyBar.js @@ -51,45 +51,8 @@ function setEnergy(energy) { Overlays.editOverlay(bar, { width: barWidth, backgroundColor: color}); } -function avatarAccelerationEnergy() { - var AVATAR_MOVEMENT_ENERGY_CONSTANT = 0.001; - var velocity = MyAvatar.getVelocity(); - var dV = Math.abs(Vec3.length(velocity) - Vec3.length(lastAvatarVelocity)); - var dE = Vec3.length(lastAvatarVelocity) * dV * AVATAR_MOVEMENT_ENERGY_CONSTANT; - lastAvatarVelocity = velocity; - return dE; -} - -function teleported() { - var MAX_AVATAR_MOVEMENT_PER_FRAME = 30.0; - var position = MyAvatar.position; - var dP = Vec3.length(Vec3.subtract(position, lastAvatarPosition)); - lastAvatarPosition = position; - return (dP > MAX_AVATAR_MOVEMENT_PER_FRAME); -} - -function audioEnergy() { - var AUDIO_ENERGY_CONSTANT = 0.000001; - return MyAvatar.audioLoudness * AUDIO_ENERGY_CONSTANT; -} - function update() { - // refill energy - currentEnergy += energyChargeRate; - - // Avatar acceleration - currentEnergy -= avatarAccelerationEnergy(); - - // Teleport cost - if (teleported()) { - currentEnergy = 0; - } - - // Making sounds - currentEnergy -= audioEnergy(); - - - currentEnergy = clamp(currentEnergy, 0, 1); + currentEnergy = clamp(MyAvatar.energy, 0, 1); setEnergy(currentEnergy); } @@ -98,5 +61,10 @@ function cleanup() { Overlays.deleteOverlay(bar); } +function energyChanged(newValue) { + Entities.currentAvatarEnergy = newValue; +} + +MyAvatar.energyChanged.connect(energyChanged); Script.update.connect(update); Script.scriptEnding.connect(cleanup); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9382f9c233..a2c74603ed 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -298,8 +298,20 @@ void MyAvatar::update(float deltaTime) { auto audio = DependencyManager::get(); head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); - - simulate(deltaTime); + + simulate(deltaTime); + + currentEnergy += energyChargeRate; + currentEnergy -= getAccelerationEnergy(); + currentEnergy -= getAudioEnergy(); + + if(didTeleport()) { + currentEnergy = 0.0f; + } + currentEnergy = max(0.0f, min(currentEnergy,1.0f)); + emit energyChanged(currentEnergy); + + } extern QByteArray avatarStateToFrame(const AvatarData* _avatar); @@ -1892,3 +1904,31 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co } } +float MyAvatar::getAccelerationEnergy() { + glm::vec3 velocity = getVelocity(); + int changeInVelocity = abs(velocity.length() - priorVelocity.length()); + float changeInEnergy = priorVelocity.length() * changeInVelocity * AVATAR_MOVEMENT_ENERGY_CONSTANT; + priorVelocity = velocity; + + return changeInEnergy; +} + +float MyAvatar::getEnergy() { + return currentEnergy; +} + +void MyAvatar::setEnergy(float value) { + currentEnergy = value; +} + +float MyAvatar::getAudioEnergy() { + return getAudioLoudness() * AUDIO_ENERGY_CONSTANT; +} + +bool MyAvatar::didTeleport() { + glm::vec3 pos = getPosition(); + glm::vec3 changeInPosition = pos - lastPosition; + lastPosition = pos; + return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME); +} + diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c272c24315..2b1136e417 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -78,6 +78,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(controller::Pose rightHandPose READ getRightHandPose) Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose) Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose) + Q_PROPERTY(float energy READ getEnergy WRITE setEnergy) public: MyAvatar(RigPointer rig); @@ -276,8 +277,10 @@ signals: void transformChanged(); void newCollisionSoundURL(const QUrl& url); void collisionWithEntity(const Collision& collision); + void energyChanged(float newEnergy); void positionGoneTo(); + private: glm::vec3 getWorldBodyPosition() const; @@ -413,9 +416,21 @@ private: AtRestDetector _hmdAtRestDetector; bool _lastIsMoving { false }; - bool _hoverReferenceCameraFacingIsCaptured { false }; glm::vec3 _hoverReferenceCameraFacing; // hmd sensor space + + float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f }; + float AUDIO_ENERGY_CONSTANT { 0.000001f }; + float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f }; + float currentEnergy { 0.0f }; + float energyChargeRate { 0.003f }; + glm::vec3 priorVelocity; + glm::vec3 lastPosition; + float getAudioEnergy(); + float getAccelerationEnergy(); + float getEnergy(); + void setEnergy(float value); + bool didTeleport(); }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 04e6cf7406..3d252abc11 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -8,7 +8,6 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - #include "EntityScriptingInterface.h" #include "EntityItemID.h" @@ -123,6 +122,20 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + auto dimensions = propertiesWithSimID.getDimensions(); + float volume = dimensions.x * dimensions.y * dimensions.z; + auto density = propertiesWithSimID.getDensity(); + auto newVelocity = propertiesWithSimID.getVelocity().length(); + float cost = calculateCost(density * volume, 0, newVelocity); + cost *= costMultiplier; + + if(cost > _currentAvatarEnergy) { + return QUuid(); + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } + EntityItemID id = EntityItemID(QUuid::createUuid()); // If we have a local entity tree set, then also update it. @@ -215,9 +228,28 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { EntityItemProperties properties = scriptSideProperties; + + auto dimensions = properties.getDimensions(); + float volume = dimensions.x * dimensions.y * dimensions.z; + auto density = properties.getDensity(); + auto newVelocity = properties.getVelocity().length(); + float oldVelocity = { 0.0f }; + EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); + + //if there is no local entity entity tree, no existing velocity, use 0. + float cost = calculateCost(density * volume, oldVelocity, newVelocity); + cost *= costMultiplier; + + if(cost > _currentAvatarEnergy) { + return QUuid(); + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } + return id; } // If we have a local entity tree set, then also update it. @@ -231,6 +263,9 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& if (!entity) { return; } + //existing entity, retrieve old velocity for check down below + oldVelocity = entity->getVelocity().length(); + if (!scriptSideProperties.parentIDChanged()) { properties.setParentID(entity->getParentID()); } @@ -246,6 +281,16 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } properties = convertLocationFromScriptSemantics(properties); updatedEntity = _entityTree->updateEntity(entityID, properties); + + float cost = calculateCost(density * volume, oldVelocity, newVelocity); + cost *= costMultiplier; + + if(cost > _currentAvatarEnergy) { + updatedEntity = false; + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } }); if (!updatedEntity) { @@ -320,6 +365,21 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { + + auto dimensions = entity->getDimensions(); + float volume = dimensions.x * dimensions.y * dimensions.z; + auto density = entity->getDensity(); + auto velocity = entity->getVelocity().length(); + float cost = calculateCost(density * volume, velocity, 0); + cost *= costMultiplier; + + if(cost > _currentAvatarEnergy) { + return; + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } + if (entity->getLocked()) { shouldDelete = false; } else { @@ -996,3 +1056,20 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) { Q_RETURN_ARG(QStringList, result), Q_ARG(QUuid, entityID)); return result; } + +float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) { + return std::abs(mass * (newVelocity - oldVelocity)); +} + +void EntityScriptingInterface::setCurrentAvatarEnergy(float energy) { + // qCDebug(entities) << "NEW AVATAR ENERGY IN ENTITY SCRIPTING INTERFACE: " << energy; + _currentAvatarEnergy = energy; +} + +float EntityScriptingInterface::getCostMultiplier() { + return costMultiplier; +} + +void EntityScriptingInterface::setCostMultiplier(float value) { + costMultiplier = value; +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 934a194360..fef000cc3d 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include @@ -57,6 +59,9 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra /// handles scripting of Entity commands from JS passed to assigned clients class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { Q_OBJECT + + Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy) + Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier) public: EntityScriptingInterface(bool bidOnSimulationOwnership); @@ -67,7 +72,7 @@ public: void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) { _entitiesScriptEngine = engine; } - + float calculateCost(float mass, float oldVelocity, float newVelocity); public slots: // returns true if the DomainServer will allow this Node/Avatar to make changes @@ -163,6 +168,7 @@ public slots: Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); Q_INVOKABLE QStringList getJointNames(const QUuid& entityID); + signals: void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -188,6 +194,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); void clearingEntities(); + void debitEnergySource(float value); private: bool actionWorker(const QUuid& entityID, std::function actor); @@ -205,7 +212,15 @@ private: EntityTreePointer _entityTree; EntitiesScriptEngineProvider* _entitiesScriptEngine { nullptr }; + bool _bidOnSimulationOwnership { false }; + float _currentAvatarEnergy = { FLT_MAX }; + float getCurrentAvatarEnergy() { return _currentAvatarEnergy; } + void setCurrentAvatarEnergy(float energy); + + float costMultiplier = { 0.01f }; + float getCostMultiplier(); + void setCostMultiplier(float value); }; #endif // hifi_EntityScriptingInterface_h