mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 09:33:45 +02:00
Merge remote-tracking branch 'clement/protocol' into atp
This commit is contained in:
commit
f057be0982
103 changed files with 2519 additions and 639 deletions
83
assignment-client/src/AssignmentAction.cpp
Normal file
83
assignment-client/src/AssignmentAction.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// AssignmentAction.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntitySimulation.h"
|
||||
|
||||
#include "AssignmentAction.h"
|
||||
|
||||
AssignmentAction::AssignmentAction(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) :
|
||||
_id(id),
|
||||
_type(type),
|
||||
_data(QByteArray()),
|
||||
_active(false),
|
||||
_ownerEntity(ownerEntity) {
|
||||
}
|
||||
|
||||
AssignmentAction::~AssignmentAction() {
|
||||
}
|
||||
|
||||
void AssignmentAction::removeFromSimulation(EntitySimulation* simulation) const {
|
||||
simulation->removeAction(_id);
|
||||
}
|
||||
|
||||
QByteArray AssignmentAction::serialize() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
void AssignmentAction::deserialize(QByteArray serializedArguments) {
|
||||
_data = serializedArguments;
|
||||
}
|
||||
|
||||
bool AssignmentAction::updateArguments(QVariantMap arguments) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::updateArguments called in assignment-client.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariantMap AssignmentAction::getArguments() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getArguments called in assignment-client.";
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentAction::getPosition() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getPosition called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentAction::setPosition(glm::vec3 position) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::setPosition called in assignment-client.";
|
||||
}
|
||||
|
||||
glm::quat AssignmentAction::getRotation() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getRotation called in assignment-client.";
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
void AssignmentAction::setRotation(glm::quat rotation) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::setRotation called in assignment-client.";
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentAction::getLinearVelocity() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getLinearVelocity called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentAction::setLinearVelocity(glm::vec3 linearVelocity) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::setLinearVelocity called in assignment-client.";
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentAction::getAngularVelocity() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getAngularVelocity called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentAction::setAngularVelocity(glm::vec3 angularVelocity) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::setAngularVelocity called in assignment-client.";
|
||||
}
|
57
assignment-client/src/AssignmentAction.h
Normal file
57
assignment-client/src/AssignmentAction.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// AssignmentAction.h
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// http://bulletphysics.org/Bullet/BulletFull/classbtActionInterface.html
|
||||
|
||||
#ifndef hifi_AssignmentAction_h
|
||||
#define hifi_AssignmentAction_h
|
||||
|
||||
#include <QUuid>
|
||||
#include <EntityItem.h>
|
||||
|
||||
#include "EntityActionInterface.h"
|
||||
|
||||
|
||||
class AssignmentAction : public EntityActionInterface {
|
||||
public:
|
||||
AssignmentAction(EntityActionType type, QUuid id, EntityItemPointer ownerEntity);
|
||||
virtual ~AssignmentAction();
|
||||
|
||||
const QUuid& getID() const { return _id; }
|
||||
virtual EntityActionType getType() { return _type; }
|
||||
virtual void removeFromSimulation(EntitySimulation* simulation) const;
|
||||
virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; }
|
||||
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; }
|
||||
virtual bool updateArguments(QVariantMap arguments);
|
||||
virtual QVariantMap getArguments();
|
||||
|
||||
virtual QByteArray serialize();
|
||||
virtual void deserialize(QByteArray serializedArguments);
|
||||
|
||||
private:
|
||||
QUuid _id;
|
||||
EntityActionType _type;
|
||||
QByteArray _data;
|
||||
|
||||
protected:
|
||||
virtual glm::vec3 getPosition();
|
||||
virtual void setPosition(glm::vec3 position);
|
||||
virtual glm::quat getRotation();
|
||||
virtual void setRotation(glm::quat rotation);
|
||||
virtual glm::vec3 getLinearVelocity();
|
||||
virtual void setLinearVelocity(glm::vec3 linearVelocity);
|
||||
virtual glm::vec3 getAngularVelocity();
|
||||
virtual void setAngularVelocity(glm::vec3 angularVelocity);
|
||||
|
||||
bool _active;
|
||||
EntityItemWeakPointer _ownerEntity;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentAction_h
|
52
assignment-client/src/AssignmentActionFactory.cpp
Normal file
52
assignment-client/src/AssignmentActionFactory.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// AssignmentActionFactory.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AssignmentActionFactory.h"
|
||||
|
||||
|
||||
EntityActionPointer assignmentActionFactory(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) {
|
||||
return (EntityActionPointer) new AssignmentAction(type, id, ownerEntity);
|
||||
}
|
||||
|
||||
|
||||
EntityActionPointer AssignmentActionFactory::factory(EntitySimulation* simulation,
|
||||
EntityActionType type,
|
||||
QUuid id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) {
|
||||
EntityActionPointer action = assignmentActionFactory(type, id, ownerEntity);
|
||||
if (action) {
|
||||
bool ok = action->updateArguments(arguments);
|
||||
if (ok) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
EntityActionPointer AssignmentActionFactory::factoryBA(EntitySimulation* simulation,
|
||||
EntityItemPointer ownerEntity,
|
||||
QByteArray data) {
|
||||
QDataStream serializedActionDataStream(data);
|
||||
EntityActionType type;
|
||||
QUuid id;
|
||||
|
||||
serializedActionDataStream >> type;
|
||||
serializedActionDataStream >> id;
|
||||
|
||||
EntityActionPointer action = assignmentActionFactory(type, id, ownerEntity);
|
||||
|
||||
if (action) {
|
||||
action->deserialize(data);
|
||||
}
|
||||
return action;
|
||||
}
|
32
assignment-client/src/AssignmentActionFactory.h
Normal file
32
assignment-client/src/AssignmentActionFactory.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// AssignmentActionFactory.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AssignmentActionFactory_h
|
||||
#define hifi_AssignmentActionFactory_h
|
||||
|
||||
#include "EntityActionFactoryInterface.h"
|
||||
#include "AssignmentAction.h"
|
||||
|
||||
class AssignmentActionFactory : public EntityActionFactoryInterface {
|
||||
public:
|
||||
AssignmentActionFactory() : EntityActionFactoryInterface() { }
|
||||
virtual ~AssignmentActionFactory() { }
|
||||
virtual EntityActionPointer factory(EntitySimulation* simulation,
|
||||
EntityActionType type,
|
||||
QUuid id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments);
|
||||
virtual EntityActionPointer factoryBA(EntitySimulation* simulation,
|
||||
EntityItemPointer ownerEntity,
|
||||
QByteArray data);
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentActionFactory_h
|
|
@ -32,6 +32,7 @@
|
|||
#include <SoundCache.h>
|
||||
|
||||
#include "AssignmentFactory.h"
|
||||
#include "AssignmentActionFactory.h"
|
||||
|
||||
#include "AssignmentClient.h"
|
||||
|
||||
|
@ -58,6 +59,9 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>();
|
||||
|
||||
DependencyManager::registerInheritance<EntityActionFactoryInterface, AssignmentActionFactory>();
|
||||
auto actionFactory = DependencyManager::set<AssignmentActionFactory>();
|
||||
|
||||
// make up a uuid for this child so the parent can tell us apart. This id will be changed
|
||||
// when the domain server hands over an assignment.
|
||||
QUuid nodeUUID = QUuid::createUuid();
|
||||
|
|
|
@ -327,6 +327,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
showStats = true;
|
||||
} else if (url.path() == "/resetStats") {
|
||||
_octreeInboundPacketProcessor->resetStats();
|
||||
_tree->resetEditStats();
|
||||
resetSendingStats();
|
||||
showStats = true;
|
||||
}
|
||||
|
@ -627,6 +628,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
// display inbound packet stats
|
||||
statsString += QString().sprintf("<b>%s Edit Statistics... <a href='/resetStats'>[RESET]</a></b>\r\n",
|
||||
getMyServerName());
|
||||
quint64 currentPacketsInQueue = _octreeInboundPacketProcessor->packetsToProcessCount();
|
||||
quint64 averageTransitTimePerPacket = _octreeInboundPacketProcessor->getAverageTransitTimePerPacket();
|
||||
quint64 averageProcessTimePerPacket = _octreeInboundPacketProcessor->getAverageProcessTimePerPacket();
|
||||
quint64 averageLockWaitTimePerPacket = _octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket();
|
||||
|
@ -635,8 +637,18 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
quint64 totalElementsProcessed = _octreeInboundPacketProcessor->getTotalElementsProcessed();
|
||||
quint64 totalPacketsProcessed = _octreeInboundPacketProcessor->getTotalPacketsProcessed();
|
||||
|
||||
quint64 averageDecodeTime = _tree->getAverageDecodeTime();
|
||||
quint64 averageLookupTime = _tree->getAverageLookupTime();
|
||||
quint64 averageUpdateTime = _tree->getAverageUpdateTime();
|
||||
quint64 averageCreateTime = _tree->getAverageCreateTime();
|
||||
quint64 averageLoggingTime = _tree->getAverageLoggingTime();
|
||||
|
||||
|
||||
float averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed;
|
||||
|
||||
statsString += QString(" Current Inbound Packets Queue: %1 packets\r\n")
|
||||
.arg(locale.toString((uint)currentPacketsInQueue).rightJustified(COLUMN_WIDTH, ' '));
|
||||
|
||||
statsString += QString(" Total Inbound Packets: %1 packets\r\n")
|
||||
.arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += QString(" Total Inbound Elements: %1 elements\r\n")
|
||||
|
@ -654,6 +666,17 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
statsString += QString(" Average Wait Lock Time/Element: %1 usecs\r\n")
|
||||
.arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
|
||||
|
||||
statsString += QString(" Average Decode Time: %1 usecs\r\n")
|
||||
.arg(locale.toString((uint)averageDecodeTime).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += QString(" Average Lookup Time: %1 usecs\r\n")
|
||||
.arg(locale.toString((uint)averageLookupTime).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += QString(" Average Update Time: %1 usecs\r\n")
|
||||
.arg(locale.toString((uint)averageUpdateTime).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += QString(" Average Create Time: %1 usecs\r\n")
|
||||
.arg(locale.toString((uint)averageCreateTime).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += QString(" Average Logging Time: %1 usecs\r\n")
|
||||
.arg(locale.toString((uint)averageLoggingTime).rightJustified(COLUMN_WIDTH, ' '));
|
||||
|
||||
|
||||
int senderNumber = 0;
|
||||
NodeToSenderStatsMap& allSenderStats = _octreeInboundPacketProcessor->getSingleSenderStats();
|
||||
|
@ -1411,6 +1434,8 @@ void OctreeServer::sendStatsPacket() {
|
|||
|
||||
static QJsonObject statsObject3;
|
||||
|
||||
statsObject3[baseName + QString(".3.inbound.data.1.packetQueue")] =
|
||||
(double)_octreeInboundPacketProcessor->packetsToProcessCount();
|
||||
statsObject3[baseName + QString(".3.inbound.data.1.totalPackets")] =
|
||||
(double)_octreeInboundPacketProcessor->getTotalPacketsProcessed();
|
||||
statsObject3[baseName + QString(".3.inbound.data.2.totalElements")] =
|
||||
|
|
91
examples/animationPerfTest.js
Normal file
91
examples/animationPerfTest.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/07/01
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var NUM_MOONS = 20;
|
||||
// 1 = 60Hz, 2 = 30Hz, 3 = 20Hz, etc
|
||||
var UPDATE_FREQUENCY_DIVISOR = 2;
|
||||
|
||||
var MAX_RANGE = 75.0;
|
||||
var LIFETIME = 600;
|
||||
var SCALE = 0.1;
|
||||
|
||||
var center = Vec3.sum(MyAvatar.position,
|
||||
Vec3.multiply(MAX_RANGE * SCALE, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var DEGREES_TO_RADIANS = Math.PI / 180.0;
|
||||
var PARTICLE_MIN_SIZE = 2.50;
|
||||
var PARTICLE_MAX_SIZE = 2.50;
|
||||
|
||||
|
||||
var planet = Entities.addEntity({
|
||||
type: "Sphere",
|
||||
position: center,
|
||||
dimensions: { x: 10 * SCALE, y: 10 * SCALE, z: 10 * SCALE },
|
||||
color: { red: 0, green: 0, blue: 255 },
|
||||
ignoreCollisions: true,
|
||||
collisionsWillMove: false,
|
||||
lifetime: LIFETIME
|
||||
});
|
||||
|
||||
var moons = [];
|
||||
|
||||
// Create initial test particles that will move according to gravity from the planets
|
||||
for (var i = 0; i < NUM_MOONS; i++) {
|
||||
var radius = PARTICLE_MIN_SIZE + Math.random() * PARTICLE_MAX_SIZE;
|
||||
radius *= SCALE;
|
||||
var gray = Math.random() * 155;
|
||||
var position = { x: 10 , y: i * 3, z: 0 };
|
||||
var color = { red: 100 + gray, green: 100 + gray, blue: 100 + gray };
|
||||
if (i == 0) {
|
||||
color = { red: 255, green: 0, blue: 0 };
|
||||
radius = 6 * SCALE
|
||||
}
|
||||
moons.push(Entities.addEntity({
|
||||
type: "Sphere",
|
||||
position: Vec3.sum(center, position),
|
||||
dimensions: { x: radius, y: radius, z: radius },
|
||||
color: color,
|
||||
ignoreCollisions: true,
|
||||
lifetime: LIFETIME,
|
||||
collisionsWillMove: false
|
||||
}));
|
||||
}
|
||||
|
||||
Script.update.connect(update);
|
||||
|
||||
function scriptEnding() {
|
||||
Entities.deleteEntity(planet);
|
||||
for (var i = 0; i < moons.length; i++) {
|
||||
Entities.deleteEntity(moons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var totalTime = 0.0;
|
||||
var updateCount = 0;
|
||||
function update(deltaTime) {
|
||||
// Apply gravitational force from planets
|
||||
totalTime += deltaTime;
|
||||
updateCount++;
|
||||
if (0 != updateCount % UPDATE_FREQUENCY_DIVISOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
var planetProperties = Entities.getEntityProperties(planet);
|
||||
var center = planetProperties.position;
|
||||
var particlePos = Entities.getEntityProperties(moons[0]).position;
|
||||
var relativePos = Vec3.subtract(particlePos.position, center);
|
||||
for (var t = 0; t < moons.length; t++) {
|
||||
var thetaDelta = (Math.PI * 2.0 / NUM_MOONS) * t;
|
||||
var y = Math.sin(totalTime + thetaDelta) * 10.0 * SCALE;
|
||||
var x = Math.cos(totalTime + thetaDelta) * 10.0 * SCALE;
|
||||
var newBasePos = Vec3.sum({ x: 0, y: y, z: x }, center);
|
||||
Entities.editEntity(moons[t], { position: newBasePos});
|
||||
}
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(scriptEnding);
|
42
examples/debug-actions.js
Normal file
42
examples/debug-actions.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
function printData(data) {
|
||||
var str = '';
|
||||
for (var key in data) {
|
||||
if (typeof data[key] == 'object') str += key + printData(data[key]) + ' ';
|
||||
else str += key + ':' + data[key] + ' ';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
function printActions(deltaTime) {
|
||||
var printed = false;
|
||||
var ids = Entities.findEntities(MyAvatar.position, 10);
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var entityID = ids[i];
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
if (actionIDs.length > 0) {
|
||||
var props = Entities.getEntityProperties(entityID);
|
||||
var output = props.name + " ";
|
||||
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
|
||||
actionID = actionIDs[actionIndex];
|
||||
actionArguments = Entities.getActionArguments(entityID, actionID);
|
||||
// output += actionArguments['type'];
|
||||
output += "(" + printData(actionArguments) + ") ";
|
||||
}
|
||||
if (!printed) {
|
||||
print("---------");
|
||||
printed = true;
|
||||
}
|
||||
print(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Script.setInterval(printActions, 5000);
|
||||
|
||||
// Script.update.connect(printActions);
|
|
@ -40,7 +40,7 @@ Avatar = function() {
|
|||
// settings
|
||||
this.headFree = true;
|
||||
this.armsFree = this.hydraCheck(); // automatically sets true to enable Hydra support - temporary fix
|
||||
this.makesFootStepSounds = true;
|
||||
this.makesFootStepSounds = false;
|
||||
this.blenderPreRotations = false; // temporary fix
|
||||
this.animationSet = undefined; // currently just one animation set
|
||||
this.setAnimationSet = function(animationSet) {
|
||||
|
|
|
@ -171,7 +171,7 @@ var mouseLook = (function () {
|
|||
}
|
||||
|
||||
function setupMenu() {
|
||||
Menu.addMenuItem({ menuName: "View", menuItemName: "Mouselook Mode", shortcutKey: "META+M",
|
||||
Menu.addMenuItem({ menuName: "View", menuItemName: "Mouselook Mode", shortcutKey: "SHIFT+M",
|
||||
afterItem: "Mirror", isCheckable: true, isChecked: false });
|
||||
}
|
||||
|
||||
|
|
|
@ -16,41 +16,60 @@ var controllerID;
|
|||
var controllerActive;
|
||||
var stickID = null;
|
||||
var actionID = nullActionID;
|
||||
// sometimes if this is run immediately the stick doesn't get created? use a timer.
|
||||
Script.setTimeout(function() {
|
||||
stickID = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx",
|
||||
compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj",
|
||||
dimensions: {x: .11, y: .11, z: 1.0},
|
||||
position: MyAvatar.getRightPalmPosition(), // initial position doesn't matter, as long as it's close
|
||||
rotation: MyAvatar.orientation,
|
||||
damping: .1,
|
||||
collisionSoundURL: "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/67LCollision07.wav",
|
||||
restitution: 0.01,
|
||||
collisionsWillMove: true
|
||||
});
|
||||
actionID = Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9},
|
||||
hand: hand,
|
||||
timeScale: 0.15});
|
||||
}, 3000);
|
||||
var makingNewStick = false;
|
||||
|
||||
function makeNewStick() {
|
||||
if (makingNewStick) {
|
||||
return;
|
||||
}
|
||||
makingNewStick = true;
|
||||
cleanUp();
|
||||
// sometimes if this is run immediately the stick doesn't get created? use a timer.
|
||||
Script.setTimeout(function() {
|
||||
stickID = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "stick",
|
||||
modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx",
|
||||
compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj",
|
||||
dimensions: {x: .11, y: .11, z: 1.0},
|
||||
position: MyAvatar.getRightPalmPosition(), // initial position doesn't matter, as long as it's close
|
||||
rotation: MyAvatar.orientation,
|
||||
damping: .1,
|
||||
collisionSoundURL: "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/67LCollision07.wav",
|
||||
restitution: 0.01,
|
||||
collisionsWillMove: true
|
||||
});
|
||||
actionID = Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9},
|
||||
hand: hand,
|
||||
timeScale: 0.15});
|
||||
if (actionID == nullActionID) {
|
||||
cleanUp();
|
||||
}
|
||||
makingNewStick = false;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
|
||||
function cleanUp() {
|
||||
Entities.deleteEntity(stickID);
|
||||
if (stickID) {
|
||||
Entities.deleteEntity(stickID);
|
||||
stickID = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function positionStick(stickOrientation) {
|
||||
var baseOffset = {x: 0.0, y: 0.0, z: -0.9};
|
||||
var offset = Vec3.multiplyQbyV(stickOrientation, baseOffset);
|
||||
Entities.updateAction(stickID, actionID, {relativePosition: offset,
|
||||
relativeRotation: stickOrientation});
|
||||
if (!Entities.updateAction(stickID, actionID, {relativePosition: offset, relativeRotation: stickOrientation})) {
|
||||
makeNewStick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
if (!stickID || actionID == nullActionID) {
|
||||
makeNewStick();
|
||||
return;
|
||||
}
|
||||
var windowCenterX = Window.innerWidth / 2;
|
||||
|
@ -89,6 +108,8 @@ function update(deltaTime){
|
|||
}
|
||||
|
||||
|
||||
|
||||
Script.scriptEnding.connect(cleanUp);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Script.update.connect(update);
|
||||
makeNewStick();
|
||||
|
|
|
@ -16,6 +16,8 @@ find_package(Qt5LinguistToolsMacros)
|
|||
|
||||
if (DEFINED ENV{JOB_ID})
|
||||
set(BUILD_SEQ $ENV{JOB_ID})
|
||||
elseif (DEFINED ENV{ghprbPullId})
|
||||
set(BUILD_SEQ "PR: $ENV{ghprbPullId} - Commit: $ENV{ghprbActualCommit}")
|
||||
else ()
|
||||
set(BUILD_SEQ "dev")
|
||||
endif ()
|
||||
|
|
|
@ -91,6 +91,7 @@
|
|||
#include <SceneScriptingInterface.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SimpleAverage.h>
|
||||
#include <SoundCache.h>
|
||||
#include <TextRenderer.h>
|
||||
#include <Tooltip.h>
|
||||
|
@ -178,6 +179,7 @@ using namespace std;
|
|||
// Starfield information
|
||||
static unsigned STARFIELD_NUM_STARS = 50000;
|
||||
static unsigned STARFIELD_SEED = 1;
|
||||
static uint8_t THROTTLED_IDLE_TIMER_DELAY = 10;
|
||||
|
||||
const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB
|
||||
|
||||
|
@ -1037,7 +1039,7 @@ void Application::showEditEntitiesHelp() {
|
|||
InfoView::show(INFO_EDIT_ENTITIES_PATH);
|
||||
}
|
||||
|
||||
void Application::resetCamerasOnResizeGL(Camera& camera, const glm::uvec2& size) {
|
||||
void Application::resetCameras(Camera& camera, const glm::uvec2& size) {
|
||||
if (OculusManager::isConnected()) {
|
||||
OculusManager::configureCamera(camera);
|
||||
} else if (TV3DManager::isConnected()) {
|
||||
|
@ -1060,7 +1062,6 @@ void Application::resizeGL() {
|
|||
if (_renderResolution != toGlm(renderSize)) {
|
||||
_renderResolution = toGlm(renderSize);
|
||||
DependencyManager::get<TextureCache>()->setFrameBufferSize(renderSize);
|
||||
resetCamerasOnResizeGL(_myCamera, _renderResolution);
|
||||
|
||||
glViewport(0, 0, _renderResolution.x, _renderResolution.y); // shouldn't this account for the menu???
|
||||
|
||||
|
@ -1068,6 +1069,8 @@ void Application::resizeGL() {
|
|||
glLoadIdentity();
|
||||
}
|
||||
|
||||
resetCameras(_myCamera, _renderResolution);
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
|
||||
auto canvasSize = _glWidget->size();
|
||||
|
@ -1784,8 +1787,24 @@ void Application::checkFPS() {
|
|||
}
|
||||
|
||||
void Application::idle() {
|
||||
PerformanceTimer perfTimer("idle");
|
||||
static SimpleAverage<float> interIdleDurations;
|
||||
static uint64_t lastIdleEnd{ 0 };
|
||||
|
||||
if (lastIdleEnd != 0) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
interIdleDurations.update(now - lastIdleEnd);
|
||||
static uint64_t lastReportTime = now;
|
||||
if ((now - lastReportTime) >= (USECS_PER_SECOND)) {
|
||||
static QString LOGLINE("Average inter-idle time: %1 us for %2 samples");
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::LogExtraTimings)) {
|
||||
qCDebug(interfaceapp_timing) << LOGLINE.arg((int)interIdleDurations.getAverage()).arg(interIdleDurations.getCount());
|
||||
}
|
||||
interIdleDurations.reset();
|
||||
lastReportTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceTimer perfTimer("idle");
|
||||
if (_aboutToQuit) {
|
||||
return; // bail early, nothing to do here.
|
||||
}
|
||||
|
@ -1828,13 +1847,15 @@ void Application::idle() {
|
|||
_idleLoopStdev.reset();
|
||||
}
|
||||
|
||||
// After finishing all of the above work, restart the idle timer, allowing 2ms to process events.
|
||||
idleTimer->start(2);
|
||||
}
|
||||
}
|
||||
// After finishing all of the above work, ensure the idle timer is set to the proper interval,
|
||||
// depending on whether we're throttling or not
|
||||
idleTimer->start(_glWidget->isThrottleRendering() ? THROTTLED_IDLE_TIMER_DELAY : 0);
|
||||
}
|
||||
|
||||
// check for any requested background downloads.
|
||||
emit checkBackgroundDownloads();
|
||||
lastIdleEnd = usecTimestampNow();
|
||||
}
|
||||
|
||||
void Application::setFullscreen(bool fullscreen) {
|
||||
|
@ -2207,6 +2228,7 @@ void Application::init() {
|
|||
|
||||
// Make sure any new sounds are loaded as soon as know about them.
|
||||
connect(tree, &EntityTree::newCollisionSoundURL, DependencyManager::get<SoundCache>().data(), &SoundCache::getSound);
|
||||
connect(_myAvatar, &MyAvatar::newCollisionSoundURL, DependencyManager::get<SoundCache>().data(), &SoundCache::getSound);
|
||||
}
|
||||
|
||||
void Application::closeMirrorView() {
|
||||
|
@ -3547,7 +3569,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
}
|
||||
//Render the sixense lasers
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseLasers)) {
|
||||
_myAvatar->renderLaserPointers();
|
||||
_myAvatar->renderLaserPointers(*renderArgs->_batch);
|
||||
}
|
||||
|
||||
if (!selfAvatarOnly) {
|
||||
|
|
|
@ -485,7 +485,7 @@ private slots:
|
|||
void setCursorVisible(bool visible);
|
||||
|
||||
private:
|
||||
void resetCamerasOnResizeGL(Camera& camera, const glm::uvec2& size);
|
||||
void resetCameras(Camera& camera, const glm::uvec2& size);
|
||||
void updateProjectionMatrix();
|
||||
void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true);
|
||||
|
||||
|
|
|
@ -18,32 +18,53 @@
|
|||
#include "InterfaceActionFactory.h"
|
||||
|
||||
|
||||
EntityActionPointer interfaceActionFactory(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) {
|
||||
switch (type) {
|
||||
case ACTION_TYPE_NONE:
|
||||
return nullptr;
|
||||
case ACTION_TYPE_OFFSET:
|
||||
return (EntityActionPointer) new ObjectActionOffset(type, id, ownerEntity);
|
||||
case ACTION_TYPE_SPRING:
|
||||
return (EntityActionPointer) new ObjectActionSpring(type, id, ownerEntity);
|
||||
case ACTION_TYPE_HOLD:
|
||||
return (EntityActionPointer) new AvatarActionHold(type, id, ownerEntity);
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
EntityActionPointer InterfaceActionFactory::factory(EntitySimulation* simulation,
|
||||
EntityActionType type,
|
||||
QUuid id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) {
|
||||
EntityActionPointer action = nullptr;
|
||||
switch (type) {
|
||||
case ACTION_TYPE_NONE:
|
||||
return nullptr;
|
||||
case ACTION_TYPE_OFFSET:
|
||||
action = (EntityActionPointer) new ObjectActionOffset(id, ownerEntity);
|
||||
break;
|
||||
case ACTION_TYPE_SPRING:
|
||||
action = (EntityActionPointer) new ObjectActionSpring(id, ownerEntity);
|
||||
break;
|
||||
case ACTION_TYPE_HOLD:
|
||||
action = (EntityActionPointer) new AvatarActionHold(id, ownerEntity);
|
||||
break;
|
||||
EntityActionPointer action = interfaceActionFactory(type, id, ownerEntity);
|
||||
if (action) {
|
||||
bool ok = action->updateArguments(arguments);
|
||||
if (ok) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ok = action->updateArguments(arguments);
|
||||
if (ok) {
|
||||
ownerEntity->addAction(simulation, action);
|
||||
return action;
|
||||
|
||||
EntityActionPointer InterfaceActionFactory::factoryBA(EntitySimulation* simulation,
|
||||
EntityItemPointer ownerEntity,
|
||||
QByteArray data) {
|
||||
QDataStream serializedArgumentStream(data);
|
||||
EntityActionType type;
|
||||
QUuid id;
|
||||
|
||||
serializedArgumentStream >> type;
|
||||
serializedArgumentStream >> id;
|
||||
|
||||
EntityActionPointer action = interfaceActionFactory(type, id, ownerEntity);
|
||||
|
||||
if (action) {
|
||||
action->deserialize(data);
|
||||
}
|
||||
|
||||
action = nullptr;
|
||||
return action;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,9 @@ public:
|
|||
QUuid id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments);
|
||||
virtual EntityActionPointer factoryBA(EntitySimulation* simulation,
|
||||
EntityItemPointer ownerEntity,
|
||||
QByteArray data);
|
||||
};
|
||||
|
||||
#endif // hifi_InterfaceActionFactory_h
|
||||
|
|
|
@ -12,3 +12,4 @@
|
|||
#include "InterfaceLogging.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(interfaceapp, "hifi.interface")
|
||||
Q_LOGGING_CATEGORY(interfaceapp_timing, "hifi.interface.timing")
|
||||
|
|
|
@ -15,5 +15,6 @@
|
|||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(interfaceapp)
|
||||
Q_DECLARE_LOGGING_CATEGORY(interfaceapp_timing)
|
||||
|
||||
#endif // hifi_InterfaceLogging_h
|
||||
|
|
|
@ -529,6 +529,7 @@ Menu::Menu() {
|
|||
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer);
|
||||
addActionToQMenuAndActionHash(timingMenu, MenuOption::RunTimingTests, 0, qApp, SLOT(runTests()));
|
||||
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings);
|
||||
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::LogExtraTimings);
|
||||
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings);
|
||||
|
||||
auto audioIO = DependencyManager::get<AudioClient>();
|
||||
|
|
|
@ -212,6 +212,7 @@ namespace MenuOption {
|
|||
const QString LodTools = "LOD Tools";
|
||||
const QString Login = "Login";
|
||||
const QString Log = "Log";
|
||||
const QString LogExtraTimings = "Log Extra Timing Details";
|
||||
const QString LowVelocityFilter = "Low Velocity Filter";
|
||||
const QString Mirror = "Mirror";
|
||||
const QString MuteAudio = "Mute Microphone";
|
||||
|
|
|
@ -449,26 +449,26 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, boo
|
|||
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
if (renderBounding && shouldRenderHead(renderArgs)) {
|
||||
_skeletonModel.renderBoundingCollisionShapes(0.7f);
|
||||
_skeletonModel.renderBoundingCollisionShapes(*renderArgs->_batch, 0.7f);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the avatar being looked at, render a little ball above their head
|
||||
if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) {
|
||||
const float LOOK_AT_INDICATOR_RADIUS = 0.03f;
|
||||
const float LOOK_AT_INDICATOR_OFFSET = 0.22f;
|
||||
const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f };
|
||||
glm::vec3 position;
|
||||
if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
|
||||
position = glm::vec3(_position.x, getDisplayNamePosition().y, _position.z);
|
||||
} else {
|
||||
position = glm::vec3(_position.x, getDisplayNamePosition().y + LOOK_AT_INDICATOR_OFFSET, _position.z);
|
||||
}
|
||||
Transform transform;
|
||||
transform.setTranslation(position);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch, LOOK_AT_INDICATOR_RADIUS
|
||||
, 15, 15, LOOK_AT_INDICATOR_COLOR);
|
||||
// If this is the avatar being looked at, render a little ball above their head
|
||||
if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) {
|
||||
const float LOOK_AT_INDICATOR_RADIUS = 0.03f;
|
||||
const float LOOK_AT_INDICATOR_OFFSET = 0.22f;
|
||||
const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f };
|
||||
glm::vec3 position;
|
||||
if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
|
||||
position = glm::vec3(_position.x, getDisplayNamePosition().y, _position.z);
|
||||
} else {
|
||||
position = glm::vec3(_position.x, getDisplayNamePosition().y + LOOK_AT_INDICATOR_OFFSET, _position.z);
|
||||
}
|
||||
Transform transform;
|
||||
transform.setTranslation(position);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch, LOOK_AT_INDICATOR_RADIUS
|
||||
, 15, 15, LOOK_AT_INDICATOR_COLOR);
|
||||
}
|
||||
|
||||
// quick check before falling into the code below:
|
||||
|
@ -694,9 +694,9 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa
|
|||
const float DESIRED_HIGHT_ON_SCREEN = 20; // In pixels (this is double on retinas)
|
||||
|
||||
// Projected point are between -1.0f and 1.0f, hence 0.5f * windowSizeY
|
||||
double pixelHeight = 0.5f * windowSizeY * glm::abs((p1.y / p1.w) - (p0.y / p0.w)); //
|
||||
float pixelHeight = 0.5f * windowSizeY * glm::abs((p1.y / p1.w) - (p0.y / p0.w)); //
|
||||
// Handles pixel density (especially for macs retina displays)
|
||||
double devicePixelRatio = qApp->getDevicePixelRatio() * qApp->getRenderResolutionScale(); // pixels / unit
|
||||
float devicePixelRatio = (float)qApp->getDevicePixelRatio() * qApp->getRenderResolutionScale(); // pixels / unit
|
||||
|
||||
// Compute correct scale to apply
|
||||
float scale = DESIRED_HIGHT_ON_SCREEN / (fontSize * pixelHeight) * devicePixelRatio;
|
||||
|
@ -708,9 +708,10 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa
|
|||
glm::vec3 worldOffset = glm::vec3(screenOffset.x, screenOffset.y, 0.0f) / (float)pixelHeight;
|
||||
|
||||
// Compute orientation
|
||||
glm::vec3 eulerAngles = ::safeEulerAngles(frustum.getOrientation());
|
||||
eulerAngles.z = 0.0f; // Cancel roll
|
||||
glm::quat orientation(eulerAngles); // back to quaternions
|
||||
glm::vec3 dPosition = frustum.getPosition() - getPosition();
|
||||
// If x and z are 0, atan(x, z) is undefined, so default to 0 degrees
|
||||
float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z);
|
||||
glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f));
|
||||
|
||||
// Set transform (The order IS important)
|
||||
result.setTranslation(textPosition);
|
||||
|
@ -1010,7 +1011,7 @@ int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) {
|
|||
int Avatar::_jointConesID = GeometryCache::UNKNOWN_ID;
|
||||
|
||||
// render a makeshift cone section that serves as a body part connecting joint spheres
|
||||
void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
|
||||
void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
|
||||
float radius1, float radius2, const glm::vec4& color) {
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
@ -1057,7 +1058,7 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
|
|||
// TODO: this is really inefficient constantly recreating these vertices buffers. It would be
|
||||
// better if the avatars cached these buffers for each of the joints they are rendering
|
||||
geometryCache->updateVertices(_jointConesID, points, color);
|
||||
geometryCache->renderVertices(gpu::TRIANGLES, _jointConesID);
|
||||
geometryCache->renderVertices(batch, gpu::TRIANGLES, _jointConesID);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ public:
|
|||
|
||||
virtual int parseDataAtOffset(const QByteArray& packet, int offset);
|
||||
|
||||
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
|
||||
static void renderJointConnectingCone( gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
|
||||
float radius1, float radius2, const glm::vec4& color);
|
||||
|
||||
virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { }
|
||||
|
|
|
@ -9,13 +9,21 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
#include "avatar/MyAvatar.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
||||
#include "AvatarActionHold.h"
|
||||
|
||||
AvatarActionHold::AvatarActionHold(QUuid id, EntityItemPointer ownerEntity) :
|
||||
ObjectActionSpring(id, ownerEntity) {
|
||||
const uint16_t AvatarActionHold::holdVersion = 1;
|
||||
|
||||
AvatarActionHold::AvatarActionHold(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) :
|
||||
ObjectActionSpring(type, id, ownerEntity),
|
||||
_relativePosition(glm::vec3(0.0f)),
|
||||
_relativeRotation(glm::quat()),
|
||||
_hand("right"),
|
||||
_mine(false)
|
||||
{
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionHold::AvatarActionHold";
|
||||
#endif
|
||||
|
@ -28,6 +36,13 @@ AvatarActionHold::~AvatarActionHold() {
|
|||
}
|
||||
|
||||
void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
|
||||
if (!_mine) {
|
||||
// if a local script isn't updating this, then we are just getting spring-action data over the wire.
|
||||
// let the super-class handle it.
|
||||
ObjectActionSpring::updateActionWorker(deltaTimeStep);
|
||||
return;
|
||||
}
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
if (!tryLockForRead()) {
|
||||
|
@ -51,6 +66,22 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
|
|||
if (!tryLockForWrite()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for NaNs
|
||||
if (position.x != position.x ||
|
||||
position.y != position.y ||
|
||||
position.z != position.z) {
|
||||
qDebug() << "AvatarActionHold::updateActionWorker -- target position includes NaN";
|
||||
return;
|
||||
}
|
||||
if (rotation.x != rotation.x ||
|
||||
rotation.y != rotation.y ||
|
||||
rotation.z != rotation.z ||
|
||||
rotation.w != rotation.w) {
|
||||
qDebug() << "AvatarActionHold::updateActionWorker -- target rotation includes NaN";
|
||||
return;
|
||||
}
|
||||
|
||||
_positionalTarget = position;
|
||||
_rotationalTarget = rotation;
|
||||
unlock();
|
||||
|
@ -76,22 +107,22 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
lockForWrite();
|
||||
if (rPOk) {
|
||||
_relativePosition = relativePosition;
|
||||
} else if (!_parametersSet) {
|
||||
} else {
|
||||
_relativePosition = glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
if (rROk) {
|
||||
_relativeRotation = relativeRotation;
|
||||
} else if (!_parametersSet) {
|
||||
} else {
|
||||
_relativeRotation = glm::quat(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
if (tSOk) {
|
||||
_linearTimeScale = timeScale;
|
||||
_angularTimeScale = timeScale;
|
||||
} else if (!_parametersSet) {
|
||||
_linearTimeScale = 0.2;
|
||||
_angularTimeScale = 0.2;
|
||||
} else {
|
||||
_linearTimeScale = 0.2f;
|
||||
_angularTimeScale = 0.2f;
|
||||
}
|
||||
|
||||
if (hOk) {
|
||||
|
@ -104,14 +135,39 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
qDebug() << "hold action -- invalid hand argument:" << hand;
|
||||
_hand = "right";
|
||||
}
|
||||
} else if (!_parametersSet) {
|
||||
} else {
|
||||
_hand = "right";
|
||||
}
|
||||
|
||||
_parametersSet = true;
|
||||
_mine = true;
|
||||
_positionalTargetSet = true;
|
||||
_rotationalTargetSet = true;
|
||||
_active = true;
|
||||
unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
QVariantMap AvatarActionHold::getArguments() {
|
||||
QVariantMap arguments;
|
||||
lockForRead();
|
||||
if (_mine) {
|
||||
arguments["relativePosition"] = glmToQMap(_relativePosition);
|
||||
arguments["relativeRotation"] = glmToQMap(_relativeRotation);
|
||||
arguments["timeScale"] = _linearTimeScale;
|
||||
arguments["hand"] = _hand;
|
||||
} else {
|
||||
unlock();
|
||||
return ObjectActionSpring::getArguments();
|
||||
}
|
||||
unlock();
|
||||
return arguments;
|
||||
}
|
||||
|
||||
|
||||
void AvatarActionHold::deserialize(QByteArray serializedArguments) {
|
||||
if (_mine) {
|
||||
return;
|
||||
}
|
||||
ObjectActionSpring::deserialize(serializedArguments);
|
||||
}
|
||||
|
|
|
@ -19,17 +19,25 @@
|
|||
|
||||
class AvatarActionHold : public ObjectActionSpring {
|
||||
public:
|
||||
AvatarActionHold(QUuid id, EntityItemPointer ownerEntity);
|
||||
AvatarActionHold(EntityActionType type, QUuid id, EntityItemPointer ownerEntity);
|
||||
virtual ~AvatarActionHold();
|
||||
|
||||
virtual EntityActionType getType() { return ACTION_TYPE_HOLD; }
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments);
|
||||
virtual QVariantMap getArguments();
|
||||
|
||||
virtual void updateActionWorker(float deltaTimeStep);
|
||||
|
||||
virtual void deserialize(QByteArray serializedArguments);
|
||||
|
||||
private:
|
||||
static const uint16_t holdVersion;
|
||||
|
||||
glm::vec3 _relativePosition;
|
||||
glm::quat _relativeRotation;
|
||||
QString _hand;
|
||||
bool _parametersSet = false;
|
||||
bool _mine = false;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarActionHold_h
|
||||
|
|
|
@ -257,6 +257,37 @@ void AvatarManager::handleOutgoingChanges(VectorOfMotionStates& motionStates) {
|
|||
|
||||
void AvatarManager::handleCollisionEvents(CollisionEvents& collisionEvents) {
|
||||
// TODO: expose avatar collision events to JS
|
||||
for (Collision collision : collisionEvents) {
|
||||
// TODO: Current physics uses null idA or idB for non-entities. The plan is to handle MOTIONSTATE_TYPE_AVATAR,
|
||||
// and then MOTIONSTATE_TYPE_MYAVATAR. As it is, this code only covers the case of my avatar (in which case one
|
||||
// if the ids will be null), and the behavior for other avatars is not specified. This has to be fleshed
|
||||
// out as soon as we use the new motionstates.
|
||||
if (collision.idA.isNull() || collision.idB.isNull()) {
|
||||
MyAvatar* myAvatar = getMyAvatar();
|
||||
const QString& collisionSoundURL = myAvatar->getCollisionSoundURL();
|
||||
if (!collisionSoundURL.isEmpty()) {
|
||||
const float velocityChange = glm::length(collision.velocityChange);
|
||||
const float MIN_AVATAR_COLLISION_ACCELERATION = 0.01;
|
||||
const bool isSound = (collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION);
|
||||
|
||||
if (!isSound) {
|
||||
// TODO: When the new motion states are used, we'll probably break from the whole loop as soon as we hit our own avatar
|
||||
// (regardless of isSound), because other users should inject for their own avatars.
|
||||
continue;
|
||||
}
|
||||
// Your avatar sound is personal to you, so let's say the "mass" part of the kinetic energy is already accounted for.
|
||||
const float energy = velocityChange * velocityChange;
|
||||
const float COLLISION_ENERGY_AT_FULL_VOLUME = 0.5f;
|
||||
const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME);
|
||||
|
||||
// For general entity collisionSoundURL, playSound supports changing the pitch for the sound based on the size of the object,
|
||||
// but most avatars are roughly the same size, so let's not be so fancy yet.
|
||||
const float AVATAR_STRETCH_FACTOR = 1.0f;
|
||||
|
||||
AudioInjector::playSound(collisionSoundURL, energyFactorOfFull, AVATAR_STRETCH_FACTOR, myAvatar->getPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) {
|
||||
|
|
|
@ -148,10 +148,6 @@ QUuid AvatarMotionState::getSimulatorID() const {
|
|||
return _avatar->getSessionUUID();
|
||||
}
|
||||
|
||||
// virtual
|
||||
void AvatarMotionState::bump() {
|
||||
}
|
||||
|
||||
// virtual
|
||||
int16_t AvatarMotionState::computeCollisionGroup() {
|
||||
return COLLISION_GROUP_OTHER_AVATAR;
|
||||
|
|
|
@ -55,7 +55,6 @@ public:
|
|||
virtual const QUuid& getObjectID() const;
|
||||
|
||||
virtual QUuid getSimulatorID() const;
|
||||
virtual void bump();
|
||||
|
||||
void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal);
|
||||
|
||||
|
|
|
@ -103,7 +103,8 @@ void Hand::resolvePenetrations() {
|
|||
}
|
||||
|
||||
void Hand::render(RenderArgs* renderArgs, bool isMine) {
|
||||
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
|
||||
// draw a green sphere at hand joint location, which is actually near the wrist)
|
||||
for (size_t i = 0; i < getNumPalms(); i++) {
|
||||
|
@ -112,31 +113,25 @@ void Hand::render(RenderArgs* renderArgs, bool isMine) {
|
|||
continue;
|
||||
}
|
||||
glm::vec3 position = palm.getPosition();
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(PALM_COLLISION_RADIUS * _owningAvatar->getScale(), 10, 10, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glPopMatrix();
|
||||
Transform transform = Transform();
|
||||
transform.setTranslation(position);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, PALM_COLLISION_RADIUS * _owningAvatar->getScale(), 10, 10, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) {
|
||||
renderHandTargets(isMine);
|
||||
renderHandTargets(renderArgs, isMine);
|
||||
}
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_RESCALE_NORMAL);
|
||||
}
|
||||
|
||||
void Hand::renderHandTargets(bool isMine) {
|
||||
glPushMatrix();
|
||||
}
|
||||
|
||||
void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
const float avatarScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getScale();
|
||||
|
||||
const float alpha = 1.0f;
|
||||
const glm::vec3 handColor(1.0, 0.0, 0.0); // Color the hand targets red to be different than skin
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_TRUE);
|
||||
|
||||
if (isMine && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHandTargets)) {
|
||||
for (size_t i = 0; i < getNumPalms(); ++i) {
|
||||
|
@ -145,12 +140,12 @@ void Hand::renderHandTargets(bool isMine) {
|
|||
continue;
|
||||
}
|
||||
glm::vec3 targetPosition = palm.getTipPosition();
|
||||
glPushMatrix();
|
||||
glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z);
|
||||
Transform transform = Transform();
|
||||
transform.setTranslation(targetPosition);
|
||||
batch.setModelTransform(transform);
|
||||
|
||||
const float collisionRadius = 0.05f;
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(collisionRadius, 10, 10, glm::vec4(0.5f,0.5f,0.5f, alpha), false);
|
||||
glPopMatrix();
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, collisionRadius, 10, 10, glm::vec4(0.5f,0.5f,0.5f, alpha), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,22 +160,19 @@ void Hand::renderHandTargets(bool isMine) {
|
|||
if (palm.isActive()) {
|
||||
glm::vec3 tip = palm.getTipPosition();
|
||||
glm::vec3 root = palm.getPosition();
|
||||
|
||||
Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS, glm::vec4(handColor.r, handColor.g, handColor.b, alpha));
|
||||
Transform transform = Transform();
|
||||
transform.setTranslation(glm::vec3());
|
||||
batch.setModelTransform(transform);
|
||||
Avatar::renderJointConnectingCone(batch, root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS, glm::vec4(handColor.r, handColor.g, handColor.b, alpha));
|
||||
|
||||
// Render sphere at palm/finger root
|
||||
glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS;
|
||||
Avatar::renderJointConnectingCone(root, offsetFromPalm, PALM_DISK_RADIUS, 0.0f, glm::vec4(handColor.r, handColor.g, handColor.b, alpha));
|
||||
glPushMatrix();
|
||||
glTranslatef(root.x, root.y, root.z);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(PALM_BALL_RADIUS, 20.0f, 20.0f, glm::vec4(handColor.r, handColor.g, handColor.b, alpha));
|
||||
glPopMatrix();
|
||||
Avatar::renderJointConnectingCone(batch, root, offsetFromPalm, PALM_DISK_RADIUS, 0.0f, glm::vec4(handColor.r, handColor.g, handColor.b, alpha));
|
||||
transform = Transform();
|
||||
transform.setTranslation(root);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, PALM_BALL_RADIUS, 20.0f, 20.0f, glm::vec4(handColor.r, handColor.g, handColor.b, alpha));
|
||||
}
|
||||
}
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ private:
|
|||
|
||||
Avatar* _owningAvatar;
|
||||
|
||||
void renderHandTargets(bool isMine);
|
||||
void renderHandTargets(RenderArgs* renderArgs, bool isMine);
|
||||
};
|
||||
|
||||
#endif // hifi_Hand_h
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
//
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include <gpu/Batch.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <GlowEffect.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -293,10 +295,8 @@ void Head::relaxLean(float deltaTime) {
|
|||
}
|
||||
|
||||
void Head::render(RenderArgs* renderArgs, float alpha, ViewFrustum* renderFrustum, bool postLighting) {
|
||||
if (postLighting) {
|
||||
if (_renderLookatVectors) {
|
||||
if (_renderLookatVectors) {
|
||||
renderLookatVectors(renderArgs, _leftEyePosition, _rightEyePosition, getCorrectedLookAtPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,17 +380,19 @@ void Head::addLeanDeltas(float sideways, float forward) {
|
|||
}
|
||||
|
||||
void Head::renderLookatVectors(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition) {
|
||||
auto& batch = *renderArgs->_batch;
|
||||
auto transform = Transform{};
|
||||
batch.setModelTransform(transform);
|
||||
batch._glLineWidth(2.0f);
|
||||
|
||||
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
|
||||
deferredLighting->bindSimpleProgram(batch);
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
DependencyManager::get<GlowEffect>()->begin(renderArgs);
|
||||
|
||||
glLineWidth(2.0);
|
||||
|
||||
glm::vec4 startColor(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
glm::vec4 endColor(1.0f, 1.0f, 1.0f, 0.0f);
|
||||
geometryCache->renderLine(leftEyePosition, lookatPosition, startColor, endColor, _leftEyeLookAtID);
|
||||
geometryCache->renderLine(rightEyePosition, lookatPosition, startColor, endColor, _rightEyeLookAtID);
|
||||
|
||||
DependencyManager::get<GlowEffect>()->end(renderArgs);
|
||||
geometryCache->renderLine(batch, leftEyePosition, lookatPosition, startColor, endColor, _leftEyeLookAtID);
|
||||
geometryCache->renderLine(batch, rightEyePosition, lookatPosition, startColor, endColor, _rightEyeLookAtID);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -70,9 +70,10 @@ float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f;
|
|||
const int SCRIPTED_MOTOR_CAMERA_FRAME = 0;
|
||||
const int SCRIPTED_MOTOR_AVATAR_FRAME = 1;
|
||||
const int SCRIPTED_MOTOR_WORLD_FRAME = 2;
|
||||
const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav";
|
||||
|
||||
const float MyAvatar::ZOOM_MIN = 0.5f;
|
||||
const float MyAvatar::ZOOM_MAX = 10.0f;
|
||||
const float MyAvatar::ZOOM_MAX = 25.0f;
|
||||
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
|
||||
|
||||
MyAvatar::MyAvatar() :
|
||||
|
@ -90,6 +91,7 @@ MyAvatar::MyAvatar() :
|
|||
_scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
|
||||
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
|
||||
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
|
||||
_collisionSoundURL(""),
|
||||
_characterController(this),
|
||||
_lookAtTargetAvatar(),
|
||||
_shouldRender(true),
|
||||
|
@ -664,6 +666,7 @@ void MyAvatar::saveData() {
|
|||
settings.endArray();
|
||||
|
||||
settings.setValue("displayName", _displayName);
|
||||
settings.setValue("collisionSoundURL", _collisionSoundURL);
|
||||
|
||||
settings.endGroup();
|
||||
}
|
||||
|
@ -789,6 +792,7 @@ void MyAvatar::loadData() {
|
|||
settings.endArray();
|
||||
|
||||
setDisplayName(settings.value("displayName").toString());
|
||||
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
|
||||
|
||||
settings.endGroup();
|
||||
}
|
||||
|
@ -1183,6 +1187,13 @@ void MyAvatar::clearScriptableSettings() {
|
|||
_scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE;
|
||||
}
|
||||
|
||||
void MyAvatar::setCollisionSoundURL(const QString& url) {
|
||||
_collisionSoundURL = url;
|
||||
if (!url.isEmpty() && (url != _collisionSoundURL)) {
|
||||
emit newCollisionSoundURL(QUrl(url));
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation,
|
||||
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
|
@ -1212,9 +1223,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bo
|
|||
if (shouldRenderHead(renderArgs)) {
|
||||
getHead()->render(renderArgs, 1.0f, renderFrustum, postLighting);
|
||||
}
|
||||
if (postLighting) {
|
||||
getHand()->render(renderArgs, true);
|
||||
}
|
||||
getHand()->render(renderArgs, true);
|
||||
}
|
||||
|
||||
void MyAvatar::setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visible) {
|
||||
|
@ -1394,7 +1403,8 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
|
|||
}
|
||||
}
|
||||
|
||||
_boomLength += _driveKeys[BOOM_OUT] - _driveKeys[BOOM_IN];
|
||||
float boomChange = _driveKeys[BOOM_OUT] - _driveKeys[BOOM_IN];
|
||||
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
|
||||
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
|
||||
|
||||
return newLocalVelocity;
|
||||
|
@ -1587,7 +1597,7 @@ void MyAvatar::updateMotionBehavior() {
|
|||
}
|
||||
|
||||
//Renders sixense laser pointers for UI selection with controllers
|
||||
void MyAvatar::renderLaserPointers() {
|
||||
void MyAvatar::renderLaserPointers(gpu::Batch& batch) {
|
||||
const float PALM_TIP_ROD_RADIUS = 0.002f;
|
||||
|
||||
//If the Oculus is enabled, we will draw a blue cursor ray
|
||||
|
@ -1600,8 +1610,10 @@ void MyAvatar::renderLaserPointers() {
|
|||
|
||||
//Scale the root vector with the avatar scale
|
||||
scaleVectorRelativeToPosition(root);
|
||||
|
||||
Avatar::renderJointConnectingCone(root, tip, PALM_TIP_ROD_RADIUS, PALM_TIP_ROD_RADIUS, glm::vec4(0, 1, 1, 1));
|
||||
Transform transform = Transform();
|
||||
transform.setTranslation(glm::vec3());
|
||||
batch.setModelTransform(transform);
|
||||
Avatar::renderJointConnectingCone(batch, root, tip, PALM_TIP_ROD_RADIUS, PALM_TIP_ROD_RADIUS, glm::vec4(0, 1, 1, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)
|
||||
Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale)
|
||||
Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame)
|
||||
Q_PROPERTY(QString collisionSoundURL READ getCollisionSoundURL WRITE setCollisionSoundURL)
|
||||
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
|
||||
|
||||
public:
|
||||
|
@ -150,6 +151,9 @@ public:
|
|||
void setScriptedMotorTimescale(float timescale);
|
||||
void setScriptedMotorFrame(QString frame);
|
||||
|
||||
const QString& getCollisionSoundURL() {return _collisionSoundURL; }
|
||||
void setCollisionSoundURL(const QString& url);
|
||||
|
||||
void clearScriptableSettings();
|
||||
|
||||
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
|
||||
|
@ -157,7 +161,7 @@ public:
|
|||
bool allowDuplicates = false, bool useSaved = true);
|
||||
|
||||
/// Renders a laser pointer for UI picking
|
||||
void renderLaserPointers();
|
||||
void renderLaserPointers(gpu::Batch& batch);
|
||||
glm::vec3 getLaserPointerTipPosition(const PalmData* palm);
|
||||
|
||||
const RecorderPointer getRecorder() const { return _recorder; }
|
||||
|
@ -204,6 +208,7 @@ public slots:
|
|||
|
||||
signals:
|
||||
void transformChanged();
|
||||
void newCollisionSoundURL(const QUrl& url);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -233,6 +238,7 @@ private:
|
|||
float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity
|
||||
int _scriptedMotorFrame;
|
||||
quint32 _motionBehaviors;
|
||||
QString _collisionSoundURL;
|
||||
|
||||
DynamicCharacterController _characterController;
|
||||
|
||||
|
|
|
@ -776,24 +776,24 @@ void SkeletonModel::resetShapePositionsToDefaultPose() {
|
|||
_boundingShape.setRotation(_rotation);
|
||||
}
|
||||
|
||||
void SkeletonModel::renderBoundingCollisionShapes(float alpha) {
|
||||
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
if (_shapes.isEmpty()) {
|
||||
// the bounding shape has not been propery computed
|
||||
// so no need to render it
|
||||
return;
|
||||
}
|
||||
glPushMatrix();
|
||||
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
|
||||
// draw a blue sphere at the capsule endpoint
|
||||
glm::vec3 endPoint;
|
||||
_boundingShape.getEndPoint(endPoint);
|
||||
endPoint = endPoint - _translation;
|
||||
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
|
||||
Transform transform = Transform();
|
||||
transform.setTranslation(endPoint);
|
||||
batch.setModelTransform(transform);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
geometryCache->renderSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS, glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||
geometryCache->renderSphere(batch, _boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS, glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||
|
||||
// draw a yellow sphere at the capsule startpoint
|
||||
glm::vec3 startPoint;
|
||||
|
@ -805,9 +805,7 @@ void SkeletonModel::renderBoundingCollisionShapes(float alpha) {
|
|||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.0f);
|
||||
Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius(), glm::vec4(0.6f, 0.8f, 0.6f, alpha));
|
||||
|
||||
glPopMatrix();
|
||||
Avatar::renderJointConnectingCone(batch, origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius(), glm::vec4(0.6f, 0.8f, 0.6f, alpha));
|
||||
}
|
||||
|
||||
bool SkeletonModel::hasSkeleton() {
|
||||
|
|
|
@ -101,7 +101,7 @@ public:
|
|||
const glm::vec3& getStandingOffset() const { return _standingOffset; }
|
||||
|
||||
void computeBoundingShape(const FBXGeometry& geometry);
|
||||
void renderBoundingCollisionShapes(float alpha);
|
||||
void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha);
|
||||
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
|
||||
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
|
||||
const glm::vec3 getBoundingShapeOffset() const { return _boundingShapeLocalOffset; }
|
||||
|
|
|
@ -249,7 +249,6 @@ static GlWindow* _outputWindow{ nullptr };
|
|||
static bool _isConnected = false;
|
||||
static ovrHmd _ovrHmd;
|
||||
static ovrFovPort _eyeFov[ovrEye_Count];
|
||||
static ovrVector3f _eyeOffset[ovrEye_Count];
|
||||
static ovrEyeRenderDesc _eyeRenderDesc[ovrEye_Count];
|
||||
static ovrSizei _renderTargetSize;
|
||||
static glm::mat4 _eyeProjection[ovrEye_Count];
|
||||
|
@ -281,6 +280,7 @@ static ovrPosef _eyeRenderPoses[ovrEye_Count];
|
|||
static ovrRecti _eyeViewports[ovrEye_Count];
|
||||
static ovrVector3f _eyeOffsets[ovrEye_Count];
|
||||
|
||||
|
||||
glm::vec3 OculusManager::getLeftEyePosition() { return _eyePositions[ovrEye_Left]; }
|
||||
glm::vec3 OculusManager::getRightEyePosition() { return _eyePositions[ovrEye_Right]; }
|
||||
|
||||
|
@ -910,4 +910,4 @@ mat4 OculusManager::getEyePose(int eye) {
|
|||
mat4 OculusManager::getHeadPose() {
|
||||
ovrTrackingState ts = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds());
|
||||
return toGlm(ts.HeadPose.ThePose);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,6 @@ void TV3DManager::display(RenderArgs* renderArgs, Camera& whichCamera) {
|
|||
glScissor(portalX, portalY, portalW, portalH);
|
||||
|
||||
glm::mat4 projection = glm::frustum<float>(eye.left, eye.right, eye.bottom, eye.top, nearZ, farZ);
|
||||
float fov = atan(1.0f / projection[1][1]);
|
||||
projection = glm::translate(projection, vec3(eye.modelTranslation, 0, 0));
|
||||
eyeCamera.setProjection(projection);
|
||||
|
||||
|
|
|
@ -234,7 +234,6 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) {
|
|||
model.setScale(vec3(mouseSize, 1.0f));
|
||||
batch.setModelTransform(model);
|
||||
bindCursorTexture(batch);
|
||||
vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f };
|
||||
geometryCache->renderUnitQuad(batch, vec4(1));
|
||||
renderArgs->_context->render(batch);
|
||||
}
|
||||
|
@ -386,8 +385,8 @@ QPoint ApplicationCompositor::getPalmClickLocation(const PalmData *palm) const {
|
|||
ndcSpacePos = glm::vec3(clipSpacePos) / clipSpacePos.w;
|
||||
}
|
||||
|
||||
rv.setX(((ndcSpacePos.x + 1.0) / 2.0) * canvasSize.x);
|
||||
rv.setY((1.0 - ((ndcSpacePos.y + 1.0) / 2.0)) * canvasSize.y);
|
||||
rv.setX(((ndcSpacePos.x + 1.0f) / 2.0f) * canvasSize.x);
|
||||
rv.setY((1.0f - ((ndcSpacePos.y + 1.0f) / 2.0f)) * canvasSize.y);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
@ -505,7 +504,7 @@ void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) {
|
|||
|
||||
// Get the angles, scaled between (-0.5,0.5)
|
||||
float xAngle = (atan2(direction.z, direction.x) + M_PI_2);
|
||||
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
|
||||
float yAngle = 0.5f - ((atan2f(direction.z, direction.y) + (float)M_PI_2));
|
||||
|
||||
// Get the pixel range over which the xAngle and yAngle are scaled
|
||||
float cursorRange = canvasSize.x * SixenseManager::getInstance().getCursorPixelRangeMult();
|
||||
|
@ -734,7 +733,7 @@ glm::vec2 ApplicationCompositor::screenToSpherical(const glm::vec2& screenPos) {
|
|||
|
||||
glm::vec2 ApplicationCompositor::sphericalToScreen(const glm::vec2& sphericalPos) {
|
||||
glm::vec2 result = sphericalPos;
|
||||
result.x *= -1.0;
|
||||
result.x *= -1.0f;
|
||||
result /= MOUSE_RANGE;
|
||||
result += 0.5f;
|
||||
result *= qApp->getCanvasSize();
|
||||
|
@ -743,7 +742,7 @@ glm::vec2 ApplicationCompositor::sphericalToScreen(const glm::vec2& sphericalPos
|
|||
|
||||
glm::vec2 ApplicationCompositor::sphericalToOverlay(const glm::vec2& sphericalPos) const {
|
||||
glm::vec2 result = sphericalPos;
|
||||
result.x *= -1.0;
|
||||
result.x *= -1.0f;
|
||||
result /= _textureFov;
|
||||
result.x /= _textureAspectRatio;
|
||||
result += 0.5f;
|
||||
|
|
|
@ -67,7 +67,6 @@ void AvatarInputs::update() {
|
|||
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
|
||||
|
||||
auto audioIO = DependencyManager::get<AudioClient>();
|
||||
const float CLIPPING_INDICATOR_TIME = 1.0f;
|
||||
const float AUDIO_METER_AVERAGING = 0.5;
|
||||
const float LOG2 = log(2.0f);
|
||||
const float METER_LOUDNESS_SCALE = 2.8f / 5.0f;
|
||||
|
@ -85,7 +84,7 @@ void AvatarInputs::update() {
|
|||
} else {
|
||||
audioLevel = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE;
|
||||
}
|
||||
if (audioLevel > 1.0) {
|
||||
if (audioLevel > 1.0f) {
|
||||
audioLevel = 1.0;
|
||||
}
|
||||
AI_UPDATE_FLOAT(audioLevel, audioLevel, 0.01);
|
||||
|
|
|
@ -127,6 +127,8 @@ void PreferencesDialog::loadPreferences() {
|
|||
_displayNameString = myAvatar->getDisplayName();
|
||||
ui.displayNameEdit->setText(_displayNameString);
|
||||
|
||||
ui.collisionSoundURLEdit->setText(myAvatar->getCollisionSoundURL());
|
||||
|
||||
ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger));
|
||||
|
||||
ui.snapshotLocationEdit->setText(Snapshot::snapshotsLocation.get());
|
||||
|
@ -204,6 +206,8 @@ void PreferencesDialog::savePreferences() {
|
|||
myAvatar->sendIdentityPacket();
|
||||
}
|
||||
|
||||
myAvatar->setCollisionSoundURL(ui.collisionSoundURLEdit->text());
|
||||
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger)
|
||||
!= ui.sendDataCheckBox->isChecked()) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::DisableActivityLogger);
|
||||
|
@ -221,8 +225,6 @@ void PreferencesDialog::savePreferences() {
|
|||
myAvatar->setLeanScale(ui.leanScaleSpin->value());
|
||||
myAvatar->setClampedTargetScale(ui.avatarScaleSpin->value());
|
||||
|
||||
Application::getInstance()->resizeGL();
|
||||
|
||||
DependencyManager::get<AvatarManager>()->getMyAvatar()->setRealWorldFieldOfView(ui.realWorldFieldOfViewSpin->value());
|
||||
|
||||
qApp->setFieldOfView(ui.fieldOfViewSpin->value());
|
||||
|
|
|
@ -122,8 +122,8 @@ void Stats::updateStats() {
|
|||
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
||||
STAT_UPDATE(packetInCount, bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond());
|
||||
STAT_UPDATE(packetOutCount, bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond());
|
||||
STAT_UPDATE_FLOAT(mbpsIn, (float)bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond() / 1000.0f, 0.01f);
|
||||
STAT_UPDATE_FLOAT(mbpsOut, (float)bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond() / 1000.0f, 0.01f);
|
||||
STAT_UPDATE_FLOAT(mbpsIn, (float)bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond() / 1000.0f, 0.01f);
|
||||
STAT_UPDATE_FLOAT(mbpsOut, (float)bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond() / 1000.0f, 0.01f);
|
||||
|
||||
// Second column: ping
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
|
||||
|
@ -136,7 +136,6 @@ void Stats::updateStats() {
|
|||
unsigned long totalPingOctree = 0;
|
||||
int octreeServerCount = 0;
|
||||
int pingOctreeMax = 0;
|
||||
int pingVoxel;
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
// TODO: this should also support entities
|
||||
if (node->getType() == NodeType::EntityServer) {
|
||||
|
@ -147,19 +146,6 @@ void Stats::updateStats() {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (octreeServerCount) {
|
||||
pingVoxel = totalPingOctree / octreeServerCount;
|
||||
}
|
||||
|
||||
//STAT_UPDATE(entitiesPing, pingVoxel);
|
||||
//if (_expanded) {
|
||||
// QString voxelMaxPing;
|
||||
// if (pingVoxel >= 0) { // Average is only meaningful if pingVoxel is valid.
|
||||
// voxelMaxPing = QString("Voxel max ping: %1").arg(pingOctreeMax);
|
||||
// } else {
|
||||
// voxelMaxPing = QString("Voxel max ping: --");
|
||||
// }
|
||||
} else {
|
||||
// -2 causes the QML to hide the ping column
|
||||
STAT_UPDATE(audioPing, -2);
|
||||
|
@ -280,15 +266,15 @@ void Stats::updateStats() {
|
|||
}
|
||||
|
||||
// Server Octree Elements
|
||||
STAT_UPDATE(serverElements, totalNodes);
|
||||
STAT_UPDATE(localElements, OctreeElement::getNodeCount());
|
||||
STAT_UPDATE(serverElements, (int)totalNodes);
|
||||
STAT_UPDATE(localElements, (int)OctreeElement::getNodeCount());
|
||||
|
||||
if (_expanded) {
|
||||
STAT_UPDATE(serverInternal, totalInternal);
|
||||
STAT_UPDATE(serverLeaves, totalLeaves);
|
||||
STAT_UPDATE(serverInternal, (int)totalInternal);
|
||||
STAT_UPDATE(serverLeaves, (int)totalLeaves);
|
||||
// Local Voxels
|
||||
STAT_UPDATE(localInternal, OctreeElement::getInternalNodeCount());
|
||||
STAT_UPDATE(localLeaves, OctreeElement::getLeafNodeCount());
|
||||
STAT_UPDATE(localInternal, (int)OctreeElement::getInternalNodeCount());
|
||||
STAT_UPDATE(localLeaves, (int)OctreeElement::getLeafNodeCount());
|
||||
// LOD Details
|
||||
STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get<LODManager>()->getLODFeedbackText());
|
||||
}
|
||||
|
|
|
@ -245,8 +245,8 @@ void UserInputMapper::assignDefaulActionScales() {
|
|||
_actionScales[YAW_RIGHT] = 1.0f; // 1 degree per unit
|
||||
_actionScales[PITCH_DOWN] = 1.0f; // 1 degree per unit
|
||||
_actionScales[PITCH_UP] = 1.0f; // 1 degree per unit
|
||||
_actionScales[BOOM_IN] = 1.0f; // 1m per unit
|
||||
_actionScales[BOOM_OUT] = 1.0f; // 1m per unit
|
||||
_actionScales[BOOM_IN] = 0.5f; // .5m per unit
|
||||
_actionScales[BOOM_OUT] = 0.5f; // .5m per unit
|
||||
_actionStates[SHIFT] = 1.0f; // on
|
||||
_actionStates[ACTION1] = 1.0f; // default
|
||||
_actionStates[ACTION2] = 1.0f; // default
|
||||
|
|
|
@ -170,3 +170,11 @@ QSizeF TextOverlay::textSize(const QString& text) const {
|
|||
|
||||
return QSizeF(extents.x, extents.y);
|
||||
}
|
||||
|
||||
void TextOverlay::setFontSize(int fontSize) {
|
||||
_fontSize = fontSize;
|
||||
|
||||
auto oldTextRenderer = _textRenderer;
|
||||
_textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT);
|
||||
delete oldTextRenderer;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public:
|
|||
void setText(const QString& text) { _text = text; }
|
||||
void setLeftMargin(int margin) { _leftMargin = margin; }
|
||||
void setTopMargin(int margin) { _topMargin = margin; }
|
||||
void setFontSize(int fontSize) { _fontSize = fontSize; }
|
||||
void setFontSize(int fontSize);
|
||||
|
||||
virtual void setProperties(const QScriptValue& properties);
|
||||
virtual TextOverlay* createClone() const;
|
||||
|
|
|
@ -189,6 +189,66 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5csu">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="avatarCollisionSoundURLLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Avatar collision sound URL <span style=" color:#909090;">(optional)</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>collisionSoundURLEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_17csu">
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="collisionSoundURLEdit">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Enter the URL of a sound to play when you bump into something</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_appearance">
|
||||
|
|
|
@ -104,7 +104,9 @@ void AutoUpdater::parseLatestVersionData() {
|
|||
}
|
||||
|
||||
void AutoUpdater::checkVersionAndNotify() {
|
||||
if (QCoreApplication::applicationVersion() == "dev" || _builds.empty()) {
|
||||
if (QCoreApplication::applicationVersion() == "dev" ||
|
||||
QCoreApplication::applicationVersion().contains("PR") ||
|
||||
_builds.empty()) {
|
||||
// No version checking is required in dev builds or when no build
|
||||
// data was found for the platform
|
||||
return;
|
||||
|
|
|
@ -56,7 +56,8 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
|
|||
batch.setModelTransform(transformToTopLeft);
|
||||
}
|
||||
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderQuad(batch, minCorner, maxCorner, backgroundColor);
|
||||
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, false, false);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, minCorner, maxCorner, backgroundColor);
|
||||
|
||||
float scale = _lineHeight / _textRenderer->getFontSize();
|
||||
transformToTopLeft.setScale(scale); // Scale to have the correct line height
|
||||
|
|
|
@ -172,22 +172,20 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
Glower glow(0.0f);
|
||||
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Web);
|
||||
static const glm::vec2 texMin(0.0f);
|
||||
static const glm::vec2 texMax(1.0f);
|
||||
glm::vec2 topLeft(-0.5f -0.5f);
|
||||
glm::vec2 bottomRight(0.5f, 0.5f);
|
||||
static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f);
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setModelTransform(getTransformToCenter());
|
||||
bool textured = false, culled = false, emissive = false;
|
||||
if (_texture) {
|
||||
batch._glActiveTexture(GL_TEXTURE0);
|
||||
batch._glBindTexture(GL_TEXTURE_2D, _texture);
|
||||
batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
textured = emissive = true;
|
||||
}
|
||||
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, true);
|
||||
|
||||
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, textured, culled, emissive);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f));
|
||||
DependencyManager::get<DeferredLightingEffect>()->releaseSimpleProgram(batch);
|
||||
}
|
||||
|
||||
void RenderableWebEntityItem::setSourceUrl(const QString& value) {
|
||||
|
|
|
@ -28,6 +28,9 @@ class EntityActionFactoryInterface : public QObject, public Dependency {
|
|||
QUuid id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) { assert(false); return nullptr; }
|
||||
virtual EntityActionPointer factoryBA(EntitySimulation* simulation,
|
||||
EntityItemPointer ownerEntity,
|
||||
QByteArray data) { assert(false); return nullptr; }
|
||||
};
|
||||
|
||||
#endif // hifi_EntityActionFactoryInterface_h
|
||||
|
|
|
@ -9,6 +9,78 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
|
||||
+-----------------------+ +-------------------+ +------------------------------+
|
||||
| | | | | |
|
||||
| EntityActionInterface | | btActionInterface | | EntityActionFactoryInterface |
|
||||
| (entities) | | (bullet) | | (entities) |
|
||||
+-----------------------+ +-------------------+ +------------------------------+
|
||||
| | | | |
|
||||
+----+ +--+ +----------+ | |
|
||||
| | | | |
|
||||
+-------------------+ +--------------+ +------------------------+ +-------------------------+
|
||||
| | | | | | | |
|
||||
| AssignmentAction | | ObjectAction | | InterfaceActionFactory | | AssignmentActionFactory |
|
||||
|(assignment client)| | (physics) | | (interface) | | (assignment client) |
|
||||
+-------------------+ +--------------+ +------------------------+ +-------------------------+
|
||||
|
|
||||
|
|
||||
|
|
||||
+--------------------+
|
||||
| |
|
||||
| ObjectActionSpring |
|
||||
| (physics) |
|
||||
+--------------------+
|
||||
|
||||
|
||||
|
||||
|
||||
An action is a callback which is registered with bullet. An action is called-back every physics
|
||||
simulation step and can do whatever it wants with the various datastructures it has available. An
|
||||
action, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that
|
||||
point by a spring.
|
||||
|
||||
In this system, an action is a property of an EntityItem (rather, an EntityItem has a property which
|
||||
encodes a list of actions). Each action has a type and some arguments. Actions can be created by a
|
||||
script or when receiving information via an EntityTree data-stream (either over the network or from an
|
||||
svo file).
|
||||
|
||||
In the interface, if an EntityItem has actions, this EntityItem will have pointers to ObjectAction
|
||||
subclass (like ObjectActionSpring) instantiations. Code in the entities library affects an action-object
|
||||
via the EntityActionInterface (which knows nothing about bullet). When the ObjectAction subclass
|
||||
instance is created, it is registered as an action with bullet. Bullet will call into code in this
|
||||
instance with the btActionInterface every physics-simulation step.
|
||||
|
||||
Because the action can exist next to the interface's EntityTree or the entity-server's EntityTree,
|
||||
parallel versions of the factories and actions are needed.
|
||||
|
||||
In an entity-server, any type of action is instantiated as an AssignmentAction. This action isn't called
|
||||
by bullet (which isn't part of an assignment-client). It does nothing but remember its type and its
|
||||
arguments. This may change as we try to make the entity-server's simple physics simulation better, but
|
||||
right now the AssignmentAction class is a place-holder.
|
||||
|
||||
The action-objects are instantiated by singleton (dependecy) subclasses of EntityActionFactoryInterface.
|
||||
In the interface, the subclass is an InterfaceActionFactory and it will produce things like
|
||||
ObjectActionSpring. In an entity-server the subclass is an AssignmentActionFactory and it always
|
||||
produces AssignmentActions.
|
||||
|
||||
Depending on the action's type, it will have various arguments. When a script changes an argument of an
|
||||
action, the argument-holding member-variables of ObjectActionSpring (in this example) are updated and
|
||||
also serialized into _actionData in the EntityItem. Each subclass of ObjectAction knows how to serialize
|
||||
and deserialize its own arguments. _actionData is what gets sent over the wire or saved in an svo file.
|
||||
When a packet-reader receives data for _actionData, it will save it in the EntityItem; this causes the
|
||||
deserializer in the ObjectAction subclass to be called with the new data, thereby updating its argument
|
||||
variables. These argument variables are used by the code which is run when bullet does a callback.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
#include "EntityActionInterface.h"
|
||||
|
@ -174,3 +246,16 @@ QString EntityActionInterface::extractStringArgument(QString objectName, QVarian
|
|||
QString v = vV.toString();
|
||||
return v;
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType)
|
||||
{
|
||||
return stream << (quint16)entityActionType;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType)
|
||||
{
|
||||
quint16 actionTypeAsInt;
|
||||
stream >> actionTypeAsInt;
|
||||
entityActionType = (EntityActionType)actionTypeAsInt;
|
||||
return stream;
|
||||
}
|
||||
|
|
|
@ -20,10 +20,10 @@ class EntitySimulation;
|
|||
|
||||
enum EntityActionType {
|
||||
// keep these synchronized with actionTypeFromString and actionTypeToString
|
||||
ACTION_TYPE_NONE,
|
||||
ACTION_TYPE_OFFSET,
|
||||
ACTION_TYPE_SPRING,
|
||||
ACTION_TYPE_HOLD
|
||||
ACTION_TYPE_NONE = 0,
|
||||
ACTION_TYPE_OFFSET = 1000,
|
||||
ACTION_TYPE_SPRING = 2000,
|
||||
ACTION_TYPE_HOLD = 3000
|
||||
};
|
||||
|
||||
|
||||
|
@ -32,10 +32,16 @@ public:
|
|||
EntityActionInterface() { }
|
||||
virtual ~EntityActionInterface() { }
|
||||
virtual const QUuid& getID() const = 0;
|
||||
virtual EntityActionType getType() { assert(false); return ACTION_TYPE_NONE; }
|
||||
|
||||
virtual void removeFromSimulation(EntitySimulation* simulation) const = 0;
|
||||
virtual const EntityItemPointer& getOwnerEntity() const = 0;
|
||||
virtual EntityItemWeakPointer getOwnerEntity() const = 0;
|
||||
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) = 0;
|
||||
virtual bool updateArguments(QVariantMap arguments) = 0;
|
||||
virtual QVariantMap getArguments() = 0;
|
||||
|
||||
virtual QByteArray serialize() = 0;
|
||||
virtual void deserialize(QByteArray serializedArguments) = 0;
|
||||
|
||||
static EntityActionType actionTypeFromString(QString actionTypeString);
|
||||
static QString actionTypeToString(EntityActionType actionType);
|
||||
|
@ -67,4 +73,7 @@ protected:
|
|||
|
||||
typedef std::shared_ptr<EntityActionInterface> EntityActionPointer;
|
||||
|
||||
QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType);
|
||||
QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType);
|
||||
|
||||
#endif // hifi_EntityActionInterface_h
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
@ -22,12 +24,17 @@
|
|||
#include <SoundCache.h>
|
||||
|
||||
#include "EntityScriptingInterface.h"
|
||||
#include "EntityItem.h"
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntityTree.h"
|
||||
#include "EntitySimulation.h"
|
||||
#include "EntityActionFactoryInterface.h"
|
||||
|
||||
|
||||
const quint64 DEFAULT_SIMULATOR_CHANGE_LOCKOUT_PERIOD = (quint64)(0.2f * USECS_PER_SECOND);
|
||||
const quint64 MAX_SIMULATOR_CHANGE_LOCKOUT_PERIOD = 2 * USECS_PER_SECOND;
|
||||
|
||||
bool EntityItem::_sendPhysicsUpdates = true;
|
||||
int EntityItem::_maxActionsDataSize = 800;
|
||||
|
||||
EntityItem::EntityItem(const EntityItemID& entityItemID) :
|
||||
_type(EntityTypes::Unknown),
|
||||
|
@ -64,8 +71,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) :
|
|||
_collisionsWillMove(ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE),
|
||||
_locked(ENTITY_ITEM_DEFAULT_LOCKED),
|
||||
_userData(ENTITY_ITEM_DEFAULT_USER_DATA),
|
||||
_simulatorID(ENTITY_ITEM_DEFAULT_SIMULATOR_ID),
|
||||
_simulatorIDChangedTime(0),
|
||||
_simulationOwner(),
|
||||
_marketplaceID(ENTITY_ITEM_DEFAULT_MARKETPLACE_ID),
|
||||
_name(ENTITY_ITEM_DEFAULT_NAME),
|
||||
_href(""),
|
||||
|
@ -86,6 +92,13 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert
|
|||
}
|
||||
|
||||
EntityItem::~EntityItem() {
|
||||
// clear out any left-over actions
|
||||
EntityTree* entityTree = _element ? _element->getTree() : nullptr;
|
||||
EntitySimulation* simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||
if (simulation) {
|
||||
clearActions(simulation);
|
||||
}
|
||||
|
||||
// these pointers MUST be correct at delete, else we probably have a dangling backpointer
|
||||
// to this EntityItem in the corresponding data structure.
|
||||
assert(!_simulated);
|
||||
|
@ -96,13 +109,16 @@ EntityItem::~EntityItem() {
|
|||
EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties;
|
||||
|
||||
requestedProperties += PROP_SIMULATION_OWNER;
|
||||
requestedProperties += PROP_POSITION;
|
||||
requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete
|
||||
requestedProperties += PROP_ROTATION;
|
||||
requestedProperties += PROP_DENSITY;
|
||||
requestedProperties += PROP_VELOCITY;
|
||||
requestedProperties += PROP_GRAVITY;
|
||||
requestedProperties += PROP_ANGULAR_VELOCITY;
|
||||
requestedProperties += PROP_ACCELERATION;
|
||||
|
||||
requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete
|
||||
requestedProperties += PROP_DENSITY;
|
||||
requestedProperties += PROP_GRAVITY;
|
||||
requestedProperties += PROP_DAMPING;
|
||||
requestedProperties += PROP_RESTITUTION;
|
||||
requestedProperties += PROP_FRICTION;
|
||||
|
@ -111,7 +127,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
requestedProperties += PROP_SCRIPT_TIMESTAMP;
|
||||
requestedProperties += PROP_COLLISION_SOUND_URL;
|
||||
requestedProperties += PROP_REGISTRATION_POINT;
|
||||
requestedProperties += PROP_ANGULAR_VELOCITY;
|
||||
requestedProperties += PROP_ANGULAR_DAMPING;
|
||||
requestedProperties += PROP_VISIBLE;
|
||||
requestedProperties += PROP_IGNORE_FOR_COLLISIONS;
|
||||
|
@ -120,10 +135,10 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
requestedProperties += PROP_USER_DATA;
|
||||
requestedProperties += PROP_MARKETPLACE_ID;
|
||||
requestedProperties += PROP_NAME;
|
||||
requestedProperties += PROP_SIMULATOR_ID;
|
||||
requestedProperties += PROP_HREF;
|
||||
requestedProperties += PROP_DESCRIPTION;
|
||||
|
||||
requestedProperties += PROP_ACTION_DATA;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -228,13 +243,16 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
// PROP_PAGED_PROPERTY,
|
||||
// PROP_CUSTOM_PROPERTIES_INCLUDED,
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray());
|
||||
APPEND_ENTITY_PROPERTY(PROP_POSITION, getPosition());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete
|
||||
APPEND_ENTITY_PROPERTY(PROP_ROTATION, getRotation());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, getVelocity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getAngularVelocity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration());
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete
|
||||
APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping());
|
||||
APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, getRestitution());
|
||||
APPEND_ENTITY_PROPERTY(PROP_FRICTION, getFriction());
|
||||
|
@ -242,19 +260,18 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, getScriptTimestamp());
|
||||
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getAngularVelocity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping());
|
||||
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible());
|
||||
APPEND_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, getIgnoreForCollisions());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, getCollisionsWillMove());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked());
|
||||
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, getSimulatorID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_NAME, getName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_HREF, getHref());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getActionData());
|
||||
|
||||
|
||||
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
|
||||
|
@ -318,8 +335,8 @@ int EntityItem::expectedBytes() {
|
|||
}
|
||||
|
||||
|
||||
// clients use this method to unpack FULL updates from entity-server
|
||||
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
||||
|
||||
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
|
||||
|
||||
// NOTE: This shouldn't happen. The only versions of the bit stream that didn't support split mtu buffers should
|
||||
|
@ -343,14 +360,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
return 0;
|
||||
}
|
||||
|
||||
// if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates.
|
||||
glm::vec3 savePosition = getPosition();
|
||||
glm::quat saveRotation = getRotation();
|
||||
glm::vec3 saveVelocity = _velocity;
|
||||
glm::vec3 saveAngularVelocity = _angularVelocity;
|
||||
|
||||
int originalLength = bytesLeftToRead;
|
||||
QByteArray originalDataBuffer((const char*)data, originalLength);
|
||||
// TODO: figure out a way to avoid the big deep copy below.
|
||||
QByteArray originalDataBuffer((const char*)data, originalLength); // big deep copy!
|
||||
|
||||
int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
|
||||
|
||||
|
@ -404,7 +416,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
qCDebug(entities) << " lastEdited =" << lastEdited;
|
||||
qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString;
|
||||
#endif
|
||||
|
||||
|
||||
quint64 lastEditedFromBuffer = 0;
|
||||
quint64 lastEditedFromBufferAdjusted = 0;
|
||||
|
||||
|
@ -451,7 +463,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
ignoreServerPacket = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ignoreServerPacket) {
|
||||
overwriteLocalData = false;
|
||||
#ifdef WANT_DEBUG
|
||||
|
@ -468,8 +480,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
_lastEdited = lastEditedFromBufferAdjusted;
|
||||
_lastEditedFromRemote = now;
|
||||
_lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer;
|
||||
|
||||
// TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed
|
||||
|
||||
// TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed
|
||||
// the properties out of the bitstream (see below))
|
||||
somethingChangedNotification(); // notify derived classes that something has changed
|
||||
}
|
||||
|
@ -489,7 +501,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
encodedUpdateDelta = updateDeltaCoder; // determine true length
|
||||
dataAt += encodedUpdateDelta.size();
|
||||
bytesRead += encodedUpdateDelta.size();
|
||||
|
||||
|
||||
// Newer bitstreams will have a last simulated and a last updated value
|
||||
quint64 lastSimulatedFromBufferAdjusted = now;
|
||||
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) {
|
||||
|
@ -512,7 +524,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
dataAt += encodedSimulatedDelta.size();
|
||||
bytesRead += encodedSimulatedDelta.size();
|
||||
}
|
||||
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
if (overwriteLocalData) {
|
||||
qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID();
|
||||
|
@ -521,47 +533,77 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// Property Flags
|
||||
QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size
|
||||
EntityPropertyFlags propertyFlags = encodedPropertyFlags;
|
||||
dataAt += propertyFlags.getEncodedLength();
|
||||
bytesRead += propertyFlags.getEncodedLength();
|
||||
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition);
|
||||
|
||||
// Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS
|
||||
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) {
|
||||
if (propertyFlags.getHasProperty(PROP_RADIUS)) {
|
||||
float fromBuffer;
|
||||
memcpy(&fromBuffer, dataAt, sizeof(fromBuffer));
|
||||
dataAt += sizeof(fromBuffer);
|
||||
bytesRead += sizeof(fromBuffer);
|
||||
if (overwriteLocalData) {
|
||||
setRadius(fromBuffer);
|
||||
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE) {
|
||||
// pack SimulationOwner and terse update properties near each other
|
||||
|
||||
// NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data
|
||||
// even when we would otherwise ignore the rest of the packet.
|
||||
|
||||
if (propertyFlags.getHasProperty(PROP_SIMULATION_OWNER)) {
|
||||
QByteArray simOwnerData;
|
||||
int bytes = OctreePacketData::unpackDataFromBytes(dataAt, simOwnerData);
|
||||
SimulationOwner newSimOwner;
|
||||
newSimOwner.fromByteArray(simOwnerData);
|
||||
dataAt += bytes;
|
||||
bytesRead += bytes;
|
||||
|
||||
if (_simulationOwner.set(newSimOwner)) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
{ // When we own the simulation we don't accept updates to the entity's transform/velocities
|
||||
// but since we're using macros below we have to temporarily modify overwriteLocalData.
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
bool weOwnIt = _simulationOwner.matchesValidID(nodeList->getSessionUUID());
|
||||
bool oldOverwrite = overwriteLocalData;
|
||||
overwriteLocalData = overwriteLocalData && !weOwnIt;
|
||||
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition);
|
||||
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation);
|
||||
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity);
|
||||
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
|
||||
READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration);
|
||||
overwriteLocalData = oldOverwrite;
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions);
|
||||
}
|
||||
READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity);
|
||||
READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity);
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation);
|
||||
READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity);
|
||||
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity);
|
||||
READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity);
|
||||
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) {
|
||||
READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping);
|
||||
READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution);
|
||||
READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction);
|
||||
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
|
||||
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
|
||||
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
|
||||
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
|
||||
} else {
|
||||
// legacy order of packing here
|
||||
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition);
|
||||
READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions);
|
||||
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation);
|
||||
READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity);
|
||||
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity);
|
||||
READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity);
|
||||
READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration);
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping);
|
||||
READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution);
|
||||
READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction);
|
||||
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
|
||||
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
|
||||
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
|
||||
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
|
||||
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping);
|
||||
READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution);
|
||||
READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction);
|
||||
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
|
||||
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
|
||||
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
|
||||
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
|
||||
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
|
||||
//READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees);
|
||||
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping);
|
||||
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible);
|
||||
READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions);
|
||||
|
@ -569,12 +611,14 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked);
|
||||
READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
|
||||
|
||||
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) {
|
||||
if (args.bitstreamVersion < VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE) {
|
||||
// this code for when there is only simulatorID and no simulation priority
|
||||
|
||||
// we always accept the server's notion of simulatorID, so we fake overwriteLocalData as true
|
||||
// before we try to READ_ENTITY_PROPERTY it
|
||||
// before we try to READ_ENTITY_PROPERTY it
|
||||
bool temp = overwriteLocalData;
|
||||
overwriteLocalData = true;
|
||||
READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID);
|
||||
READ_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, QUuid, updateSimulatorID);
|
||||
overwriteLocalData = temp;
|
||||
}
|
||||
|
||||
|
@ -587,12 +631,15 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref);
|
||||
READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription);
|
||||
|
||||
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
|
||||
READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setActionData);
|
||||
|
||||
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
|
||||
propertyFlags, overwriteLocalData);
|
||||
|
||||
////////////////////////////////////
|
||||
// WARNING: Do not add stream content here after the subclass. Always add it before the subclass
|
||||
//
|
||||
// NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover
|
||||
// NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover
|
||||
// by doing this parsing here... but it's not likely going to fully recover the content.
|
||||
//
|
||||
// TODO: Remove this conde once we've sufficiently migrated content past this damaged version
|
||||
|
@ -614,7 +661,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
#ifdef WANT_DEBUG
|
||||
qCDebug(entities) << "skipTimeForward:" << skipTimeForward;
|
||||
#endif
|
||||
|
||||
// we want to extrapolate the motion forward to compensate for packet travel time, but
|
||||
// we don't want the side effect of flag setting.
|
||||
simulateKinematicMotion(skipTimeForward, false);
|
||||
|
@ -624,15 +670,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid& myNodeID = nodeList->getSessionUUID();
|
||||
if (overwriteLocalData) {
|
||||
if (_simulatorID == myNodeID && !_simulatorID.isNull()) {
|
||||
// we own the simulation, so we keep our transform+velocities and remove any related dirty flags
|
||||
// rather than accept the values in the packet
|
||||
setPosition(savePosition);
|
||||
setRotation(saveRotation);
|
||||
_velocity = saveVelocity;
|
||||
_angularVelocity = saveAngularVelocity;
|
||||
_dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES);
|
||||
} else {
|
||||
if (!_simulationOwner.matchesValidID(myNodeID)) {
|
||||
|
||||
_lastSimulated = now;
|
||||
}
|
||||
}
|
||||
|
@ -759,6 +798,10 @@ void EntityItem::simulate(const quint64& now) {
|
|||
}
|
||||
|
||||
void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) {
|
||||
if (hasActions()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasAngularVelocity()) {
|
||||
// angular damping
|
||||
if (_angularDamping > 0.0f) {
|
||||
|
@ -770,7 +813,7 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) {
|
|||
}
|
||||
|
||||
float angularSpeed = glm::length(_angularVelocity);
|
||||
|
||||
|
||||
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.0017453f; // 0.0017453 rad/sec = 0.1f degrees/sec
|
||||
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
|
||||
if (setFlags && angularSpeed > 0.0f) {
|
||||
|
@ -778,8 +821,8 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) {
|
|||
}
|
||||
_angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else {
|
||||
// for improved agreement with the way Bullet integrates rotations we use an approximation
|
||||
// and break the integration into bullet-sized substeps
|
||||
// for improved agreement with the way Bullet integrates rotations we use an approximation
|
||||
// and break the integration into bullet-sized substeps
|
||||
glm::quat rotation = getRotation();
|
||||
float dt = timeElapsed;
|
||||
while (dt > PHYSICS_ENGINE_FIXED_SUBSTEP) {
|
||||
|
@ -892,6 +935,7 @@ EntityItemProperties EntityItem::getProperties() const {
|
|||
|
||||
properties._type = getType();
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulationOwner, getSimulationOwner);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPosition);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensions); // NOTE: radius is obsolete
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation);
|
||||
|
@ -917,11 +961,11 @@ EntityItemProperties EntityItem::getProperties() const {
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulatorID, getSimulatorID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getActionData);
|
||||
|
||||
properties._defaultSettings = false;
|
||||
|
||||
|
@ -947,6 +991,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
bool somethingChanged = false;
|
||||
|
||||
// these affect TerseUpdate properties
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePosition);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity);
|
||||
|
@ -968,7 +1013,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, updateCreated);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, updateSimulatorID);
|
||||
|
||||
// non-simulation properties below
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
|
||||
|
@ -983,6 +1027,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setActionData);
|
||||
|
||||
if (somethingChanged) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
|
@ -1343,52 +1388,115 @@ void EntityItem::updateCreated(uint64_t value) {
|
|||
}
|
||||
}
|
||||
|
||||
void EntityItem::setSimulatorID(const QUuid& value) {
|
||||
_simulatorID = value;
|
||||
_simulatorIDChangedTime = usecTimestampNow();
|
||||
void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) {
|
||||
_simulationOwner.set(id, priority);
|
||||
}
|
||||
|
||||
void EntityItem::setSimulationOwner(const SimulationOwner& owner) {
|
||||
_simulationOwner.set(owner);
|
||||
}
|
||||
|
||||
void EntityItem::updateSimulatorID(const QUuid& value) {
|
||||
if (_simulatorID != value) {
|
||||
_simulatorID = value;
|
||||
_simulatorIDChangedTime = usecTimestampNow();
|
||||
if (_simulationOwner.setID(value)) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID;
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::clearSimulationOwnership() {
|
||||
_simulationOwner.clear();
|
||||
// don't bother setting the DIRTY_SIMULATOR_ID flag because clearSimulationOwnership()
|
||||
// is only ever called entity-server-side and the flags are only used client-side
|
||||
//_dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID;
|
||||
|
||||
}
|
||||
|
||||
bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer action) {
|
||||
checkWaitingToRemove(simulation);
|
||||
if (!checkWaitingActionData(simulation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = addActionInternal(simulation, action);
|
||||
if (!result) {
|
||||
removeAction(simulation, action->getID());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPointer action) {
|
||||
assert(action);
|
||||
assert(simulation);
|
||||
auto actionOwnerEntity = action->getOwnerEntity().lock();
|
||||
assert(actionOwnerEntity);
|
||||
assert(actionOwnerEntity.get() == this);
|
||||
|
||||
const QUuid& actionID = action->getID();
|
||||
assert(!_objectActions.contains(actionID) || _objectActions[actionID] == action);
|
||||
_objectActions[actionID] = action;
|
||||
|
||||
assert(action->getOwnerEntity().get() == this);
|
||||
|
||||
simulation->addAction(action);
|
||||
|
||||
return false;
|
||||
bool success;
|
||||
QByteArray newDataCache = serializeActions(success);
|
||||
if (success) {
|
||||
_allActionsDataCache = newDataCache;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments) {
|
||||
checkWaitingToRemove(simulation);
|
||||
if (!checkWaitingActionData(simulation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_objectActions.contains(actionID)) {
|
||||
return false;
|
||||
}
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
return action->updateArguments(arguments);
|
||||
bool success = action->updateArguments(arguments);
|
||||
|
||||
if (success) {
|
||||
_allActionsDataCache = serializeActions(success);
|
||||
} else {
|
||||
qDebug() << "EntityItem::updateAction failed";
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionID) {
|
||||
checkWaitingToRemove(simulation);
|
||||
if (!checkWaitingActionData(simulation)) {
|
||||
return false;;
|
||||
}
|
||||
|
||||
return removeActionInternal(actionID);
|
||||
}
|
||||
|
||||
bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* simulation) {
|
||||
if (_objectActions.contains(actionID)) {
|
||||
if (!simulation) {
|
||||
EntityTree* entityTree = _element ? _element->getTree() : nullptr;
|
||||
simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||
}
|
||||
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
_objectActions.remove(actionID);
|
||||
action->setOwnerEntity(nullptr);
|
||||
action->removeFromSimulation(simulation);
|
||||
return true;
|
||||
_objectActions.remove(actionID);
|
||||
|
||||
if (simulation) {
|
||||
action->removeFromSimulation(simulation);
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
_allActionsDataCache = serializeActions(success);
|
||||
return success;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EntityItem::clearActions(EntitySimulation* simulation) {
|
||||
bool EntityItem::clearActions(EntitySimulation* simulation) {
|
||||
_waitingActionData.clear();
|
||||
QHash<QUuid, EntityActionPointer>::iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
const QUuid id = i.key();
|
||||
|
@ -1397,4 +1505,150 @@ void EntityItem::clearActions(EntitySimulation* simulation) {
|
|||
action->setOwnerEntity(nullptr);
|
||||
action->removeFromSimulation(simulation);
|
||||
}
|
||||
_actionsToRemove.clear();
|
||||
_allActionsDataCache.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EntityItem::deserializeActions(QByteArray allActionsData, EntitySimulation* simulation) const {
|
||||
bool success = true;
|
||||
QVector<QByteArray> serializedActions;
|
||||
if (allActionsData.size() > 0) {
|
||||
QDataStream serializedActionsStream(allActionsData);
|
||||
serializedActionsStream >> serializedActions;
|
||||
}
|
||||
|
||||
// Keep track of which actions got added or updated by the new actionData
|
||||
QSet<QUuid> updated;
|
||||
|
||||
EntityTree* entityTree = _element ? _element->getTree() : nullptr;
|
||||
if (!simulation) {
|
||||
simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||
}
|
||||
|
||||
if (simulation && entityTree) {
|
||||
foreach(QByteArray serializedAction, serializedActions) {
|
||||
QDataStream serializedActionStream(serializedAction);
|
||||
EntityActionType actionType;
|
||||
QUuid actionID;
|
||||
serializedActionStream >> actionType;
|
||||
serializedActionStream >> actionID;
|
||||
updated << actionID;
|
||||
|
||||
if (_objectActions.contains(actionID)) {
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
// TODO: make sure types match? there isn't currently a way to
|
||||
// change the type of an existing action.
|
||||
action->deserialize(serializedAction);
|
||||
} else {
|
||||
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
|
||||
if (simulation) {
|
||||
EntityItemPointer entity = entityTree->findEntityByEntityItemID(_id);
|
||||
EntityActionPointer action = actionFactory->factoryBA(simulation, entity, serializedAction);
|
||||
if (action) {
|
||||
entity->addActionInternal(simulation, action);
|
||||
}
|
||||
} else {
|
||||
// we can't yet add the action. This method will be called later.
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove any actions that weren't included in the new data.
|
||||
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
const QUuid id = i.key();
|
||||
if (!updated.contains(id)) {
|
||||
_actionsToRemove << id;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
// no simulation
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool EntityItem::checkWaitingActionData(EntitySimulation* simulation) const {
|
||||
if (_waitingActionData.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
bool success = deserializeActions(_waitingActionData, simulation);
|
||||
if (success) {
|
||||
_waitingActionData.clear();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void EntityItem::checkWaitingToRemove(EntitySimulation* simulation) {
|
||||
foreach(QUuid actionID, _actionsToRemove) {
|
||||
removeActionInternal(actionID, simulation);
|
||||
}
|
||||
_actionsToRemove.clear();
|
||||
}
|
||||
|
||||
void EntityItem::setActionData(QByteArray actionData) {
|
||||
checkWaitingToRemove();
|
||||
bool success = deserializeActions(actionData);
|
||||
_allActionsDataCache = actionData;
|
||||
if (success) {
|
||||
_waitingActionData.clear();
|
||||
} else {
|
||||
_waitingActionData = actionData;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray EntityItem::serializeActions(bool& success) const {
|
||||
QByteArray result;
|
||||
if (!checkWaitingActionData()) {
|
||||
return _waitingActionData;
|
||||
}
|
||||
|
||||
if (_objectActions.size() == 0) {
|
||||
success = true;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QVector<QByteArray> serializedActions;
|
||||
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
const QUuid id = i.key();
|
||||
EntityActionPointer action = _objectActions[id];
|
||||
QByteArray bytesForAction = action->serialize();
|
||||
serializedActions << bytesForAction;
|
||||
i++;
|
||||
}
|
||||
|
||||
QDataStream serializedActionsStream(&result, QIODevice::WriteOnly);
|
||||
serializedActionsStream << serializedActions;
|
||||
|
||||
if (result.size() >= _maxActionsDataSize) {
|
||||
success = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const QByteArray EntityItem::getActionData() const {
|
||||
return _allActionsDataCache;
|
||||
}
|
||||
|
||||
QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const {
|
||||
QVariantMap result;
|
||||
|
||||
if (!checkWaitingActionData()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_objectActions.contains(actionID)) {
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
result = action->getArguments();
|
||||
result["type"] = EntityActionInterface::actionTypeToString(action->getType());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "EntityItemProperties.h"
|
||||
#include "EntityItemPropertiesDefaults.h"
|
||||
#include "EntityTypes.h"
|
||||
#include "SimulationOwner.h"
|
||||
|
||||
class EntitySimulation;
|
||||
class EntityTreeElement;
|
||||
|
@ -60,7 +61,6 @@ const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f;
|
|||
const float ACTIVATION_GRAVITY_DELTA = 0.1f;
|
||||
const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f;
|
||||
|
||||
|
||||
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
|
||||
#define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { };
|
||||
|
||||
|
@ -68,7 +68,6 @@ const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f;
|
|||
#define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10))
|
||||
#define debugTreeVector(V) V << "[" << V << " in meters ]"
|
||||
|
||||
|
||||
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
|
||||
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
|
||||
/// one directly, instead you must only construct one of it's derived classes with additional features.
|
||||
|
@ -92,8 +91,9 @@ public:
|
|||
DIRTY_LIFETIME = 0x0100,
|
||||
DIRTY_UPDATEABLE = 0x0200,
|
||||
DIRTY_MATERIAL = 0x00400,
|
||||
DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object
|
||||
DIRTY_SIMULATOR_ID = 0x1000,
|
||||
DIRTY_PHYSICS_ACTIVATION = 0x0800, // should activate object in physics engine
|
||||
DIRTY_SIMULATOR_OWNERSHIP = 0x1000, // should claim simulator ownership
|
||||
DIRTY_SIMULATOR_ID = 0x2000, // the simulatorID has changed
|
||||
DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION,
|
||||
DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY
|
||||
};
|
||||
|
@ -317,11 +317,16 @@ public:
|
|||
|
||||
const QString& getUserData() const { return _userData; }
|
||||
void setUserData(const QString& value) { _userData = value; }
|
||||
|
||||
QUuid getSimulatorID() const { return _simulatorID; }
|
||||
void setSimulatorID(const QUuid& value);
|
||||
|
||||
const SimulationOwner& getSimulationOwner() const { return _simulationOwner; }
|
||||
void setSimulationOwner(const QUuid& id, quint8 priority);
|
||||
void setSimulationOwner(const SimulationOwner& owner);
|
||||
void promoteSimulationPriority(quint8 priority);
|
||||
|
||||
quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); }
|
||||
QUuid getSimulatorID() const { return _simulationOwner.getID(); }
|
||||
void updateSimulatorID(const QUuid& value);
|
||||
quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; }
|
||||
void clearSimulationOwnership();
|
||||
|
||||
const QString& getMarketplaceID() const { return _marketplaceID; }
|
||||
void setMarketplaceID(const QString& value) { _marketplaceID = value; }
|
||||
|
@ -358,7 +363,7 @@ public:
|
|||
virtual void updateShapeType(ShapeType type) { /* do nothing */ }
|
||||
|
||||
uint32_t getDirtyFlags() const { return _dirtyFlags; }
|
||||
void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; }
|
||||
void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; }
|
||||
|
||||
bool isMoving() const;
|
||||
|
||||
|
@ -379,10 +384,17 @@ public:
|
|||
|
||||
void getAllTerseUpdateProperties(EntityItemProperties& properties) const;
|
||||
|
||||
void flagForOwnership() { _dirtyFlags |= DIRTY_SIMULATOR_OWNERSHIP; }
|
||||
|
||||
bool addAction(EntitySimulation* simulation, EntityActionPointer action);
|
||||
bool updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments);
|
||||
bool removeAction(EntitySimulation* simulation, const QUuid& actionID);
|
||||
void clearActions(EntitySimulation* simulation);
|
||||
bool clearActions(EntitySimulation* simulation);
|
||||
void setActionData(QByteArray actionData);
|
||||
const QByteArray getActionData() const;
|
||||
bool hasActions() { return !_objectActions.empty(); }
|
||||
QList<QUuid> getActionIDs() { return _objectActions.keys(); }
|
||||
QVariantMap getActionArguments(const QUuid& actionID) const;
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -426,8 +438,7 @@ protected:
|
|||
bool _collisionsWillMove;
|
||||
bool _locked;
|
||||
QString _userData;
|
||||
QUuid _simulatorID; // id of Node which is currently responsible for simulating this Entity
|
||||
quint64 _simulatorIDChangedTime; // when was _simulatorID last updated?
|
||||
SimulationOwner _simulationOwner;
|
||||
QString _marketplaceID;
|
||||
QString _name;
|
||||
QString _href; //Hyperlink href
|
||||
|
@ -457,7 +468,20 @@ protected:
|
|||
void* _physicsInfo = nullptr; // set by EntitySimulation
|
||||
bool _simulated; // set by EntitySimulation
|
||||
|
||||
bool addActionInternal(EntitySimulation* simulation, EntityActionPointer action);
|
||||
bool removeActionInternal(const QUuid& actionID, EntitySimulation* simulation = nullptr);
|
||||
bool deserializeActions(QByteArray allActionsData, EntitySimulation* simulation = nullptr) const;
|
||||
QByteArray serializeActions(bool& success) const;
|
||||
QHash<QUuid, EntityActionPointer> _objectActions;
|
||||
static int _maxActionsDataSize;
|
||||
mutable QByteArray _allActionsDataCache;
|
||||
// when an entity-server starts up, EntityItem::setActionData is called before the entity-tree is
|
||||
// ready. This means we can't find our EntityItemPointer or add the action to the simulation. These
|
||||
// are used to keep track of and work around this situation.
|
||||
bool checkWaitingActionData(EntitySimulation* simulation = nullptr) const;
|
||||
void checkWaitingToRemove(EntitySimulation* simulation = nullptr);
|
||||
mutable QByteArray _waitingActionData;
|
||||
mutable QSet<QUuid> _actionsToRemove;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityItem_h
|
||||
|
|
|
@ -38,7 +38,7 @@ EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM -
|
|||
EntityItemProperties::EntityItemProperties() :
|
||||
|
||||
CONSTRUCT_PROPERTY(visible, ENTITY_ITEM_DEFAULT_VISIBLE),
|
||||
CONSTRUCT_PROPERTY(position, 0),
|
||||
CONSTRUCT_PROPERTY(position, 0.0f),
|
||||
CONSTRUCT_PROPERTY(dimensions, ENTITY_ITEM_DEFAULT_DIMENSIONS),
|
||||
CONSTRUCT_PROPERTY(rotation, ENTITY_ITEM_DEFAULT_ROTATION),
|
||||
CONSTRUCT_PROPERTY(density, ENTITY_ITEM_DEFAULT_DENSITY),
|
||||
|
@ -73,7 +73,7 @@ CONSTRUCT_PROPERTY(locked, ENTITY_ITEM_DEFAULT_LOCKED),
|
|||
CONSTRUCT_PROPERTY(textures, ""),
|
||||
CONSTRUCT_PROPERTY(animationSettings, ""),
|
||||
CONSTRUCT_PROPERTY(userData, ENTITY_ITEM_DEFAULT_USER_DATA),
|
||||
CONSTRUCT_PROPERTY(simulatorID, ENTITY_ITEM_DEFAULT_SIMULATOR_ID),
|
||||
CONSTRUCT_PROPERTY(simulationOwner, SimulationOwner()),
|
||||
CONSTRUCT_PROPERTY(text, TextEntityItem::DEFAULT_TEXT),
|
||||
CONSTRUCT_PROPERTY(lineHeight, TextEntityItem::DEFAULT_LINE_HEIGHT),
|
||||
CONSTRUCT_PROPERTY(textColor, TextEntityItem::DEFAULT_TEXT_COLOR),
|
||||
|
@ -100,7 +100,7 @@ CONSTRUCT_PROPERTY(sourceUrl, ""),
|
|||
CONSTRUCT_PROPERTY(lineWidth, LineEntityItem::DEFAULT_LINE_WIDTH),
|
||||
CONSTRUCT_PROPERTY(linePoints, QVector<glm::vec3>()),
|
||||
CONSTRUCT_PROPERTY(faceCamera, TextEntityItem::DEFAULT_FACE_CAMERA),
|
||||
|
||||
CONSTRUCT_PROPERTY(actionData, QByteArray()),
|
||||
|
||||
_id(UNKNOWN_ENTITY_ID),
|
||||
_idSet(false),
|
||||
|
@ -183,7 +183,6 @@ QString EntityItemProperties::getAnimationSettings() const {
|
|||
|
||||
void EntityItemProperties::setCreated(QDateTime &v) {
|
||||
_created = v.toMSecsSinceEpoch() * 1000; // usec per msec
|
||||
qDebug() << "EntityItemProperties::setCreated QDateTime" << v << _created;
|
||||
}
|
||||
|
||||
void EntityItemProperties::debugDump() const {
|
||||
|
@ -289,8 +288,8 @@ void EntityItemProperties::setBackgroundModeFromString(const QString& background
|
|||
EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||
EntityPropertyFlags changedProperties;
|
||||
|
||||
CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions);
|
||||
CHECK_PROPERTY_CHANGE(PROP_POSITION, position);
|
||||
CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions);
|
||||
CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation);
|
||||
CHECK_PROPERTY_CHANGE(PROP_DENSITY, density);
|
||||
CHECK_PROPERTY_CHANGE(PROP_VELOCITY, velocity);
|
||||
|
@ -324,7 +323,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked);
|
||||
CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures);
|
||||
CHECK_PROPERTY_CHANGE(PROP_USER_DATA, userData);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SIMULATOR_ID, simulatorID);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SIMULATION_OWNER, simulationOwner);
|
||||
CHECK_PROPERTY_CHANGE(PROP_TEXT, text);
|
||||
CHECK_PROPERTY_CHANGE(PROP_LINE_HEIGHT, lineHeight);
|
||||
CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor);
|
||||
|
@ -353,6 +352,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_HREF, href);
|
||||
CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description);
|
||||
CHECK_PROPERTY_CHANGE(PROP_FACE_CAMERA, faceCamera);
|
||||
CHECK_PROPERTY_CHANGE(PROP_ACTION_DATA, actionData);
|
||||
|
||||
changedProperties += _stage.getChangedProperties();
|
||||
changedProperties += _atmosphere.getChangedProperties();
|
||||
|
@ -419,7 +419,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(locked);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(textures);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(userData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(simulatorID, getSimulatorIDAsString());
|
||||
//COPY_PROPERTY_TO_QSCRIPTVALUE(simulationOwner); // TODO: expose this for JSON saves?
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(text);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(lineHeight);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(textColor, getTextColor());
|
||||
|
@ -450,6 +450,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(href);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(description);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(faceCamera);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(actionData);
|
||||
|
||||
// Sitting properties support
|
||||
if (!skipDefaults) {
|
||||
|
@ -563,6 +564,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(href, QString, setHref);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(description, QString, setDescription);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(faceCamera, bool, setFaceCamera);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(actionData, QByteArray, setActionData);
|
||||
|
||||
if (!honorReadOnly) {
|
||||
// this is used by the json reader to set things that we don't want javascript to able to affect.
|
||||
|
@ -570,7 +572,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
auto result = QDateTime::fromMSecsSinceEpoch(_created / 1000, Qt::UTC); // usec per msec
|
||||
return result;
|
||||
});
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(simulatorID, QUuid, setSimulatorID);
|
||||
// TODO: expose this to QScriptValue for JSON saves?
|
||||
//COPY_PROPERTY_FROM_QSCRIPTVALUE(simulationOwner, ???, setSimulatorPriority);
|
||||
}
|
||||
|
||||
_stage.copyFromScriptValue(object, _defaultSettings);
|
||||
|
@ -705,6 +708,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
// PROP_PAGED_PROPERTY,
|
||||
// PROP_CUSTOM_PROPERTIES_INCLUDED,
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray());
|
||||
APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete
|
||||
APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation());
|
||||
|
@ -727,7 +731,6 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, properties.getCollisionsWillMove());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LOCKED, properties.getLocked());
|
||||
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, properties.getUserData());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, properties.getSimulatorID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_HREF, properties.getHref());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription());
|
||||
|
||||
|
@ -814,6 +817,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
|
||||
}
|
||||
if (propertyCount > 0) {
|
||||
int endOfEntityItemData = packetData->getUncompressedByteOffset();
|
||||
|
@ -959,7 +963,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
EntityPropertyFlags propertyFlags = encodedPropertyFlags;
|
||||
dataAt += propertyFlags.getEncodedLength();
|
||||
processedBytes += propertyFlags.getEncodedLength();
|
||||
|
||||
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation);
|
||||
|
@ -982,7 +987,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONS_WILL_MOVE, bool, setCollisionsWillMove);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATOR_ID, QUuid, setSimulatorID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HREF, QString, setHref);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription);
|
||||
|
||||
|
@ -1064,6 +1068,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
@ -1098,6 +1103,7 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt
|
|||
}
|
||||
|
||||
void EntityItemProperties::markAllChanged() {
|
||||
_simulationOwnerChanged = true;
|
||||
_positionChanged = true;
|
||||
_dimensionsChanged = true;
|
||||
_rotationChanged = true;
|
||||
|
@ -1110,7 +1116,6 @@ void EntityItemProperties::markAllChanged() {
|
|||
_frictionChanged = true;
|
||||
_lifetimeChanged = true;
|
||||
_userDataChanged = true;
|
||||
_simulatorIDChanged = true;
|
||||
_scriptChanged = true;
|
||||
_scriptTimestampChanged = true;
|
||||
_collisionSoundURLChanged = true;
|
||||
|
@ -1175,9 +1180,8 @@ void EntityItemProperties::markAllChanged() {
|
|||
|
||||
_hrefChanged = true;
|
||||
_descriptionChanged = true;
|
||||
|
||||
_faceCameraChanged = true;
|
||||
|
||||
_actionDataChanged = true;
|
||||
}
|
||||
|
||||
/// The maximum bounding cube for the entity, independent of it's rotation.
|
||||
|
@ -1227,3 +1231,27 @@ bool EntityItemProperties::hasTerseUpdateChanges() const {
|
|||
// a TerseUpdate includes the transform and its derivatives
|
||||
return _positionChanged || _velocityChanged || _rotationChanged || _angularVelocityChanged || _accelerationChanged;
|
||||
}
|
||||
|
||||
bool EntityItemProperties::hasMiscPhysicsChanges() const {
|
||||
return _gravityChanged || _dimensionsChanged || _densityChanged || _frictionChanged
|
||||
|| _restitutionChanged || _dampingChanged || _angularDampingChanged || _registrationPointChanged ||
|
||||
_compoundShapeURLChanged || _collisionsWillMoveChanged || _ignoreForCollisionsChanged;
|
||||
}
|
||||
|
||||
void EntityItemProperties::clearSimulationOwner() {
|
||||
_simulationOwner.clear();
|
||||
_simulationOwnerChanged = true;
|
||||
}
|
||||
|
||||
void EntityItemProperties::setSimulationOwner(const QUuid& id, uint8_t priority) {
|
||||
if (!_simulationOwner.matchesValidID(id) || _simulationOwner.getPriority() != priority) {
|
||||
_simulationOwner.set(id, priority);
|
||||
_simulationOwnerChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItemProperties::setSimulationOwner(const QByteArray& data) {
|
||||
if (_simulationOwner.fromByteArray(data)) {
|
||||
_simulationOwnerChanged = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "EntityItemPropertiesMacros.h"
|
||||
#include "EntityTypes.h"
|
||||
#include "EntityPropertyFlags.h"
|
||||
#include "SimulationOwner.h"
|
||||
#include "SkyboxPropertyGroup.h"
|
||||
#include "StagePropertyGroup.h"
|
||||
|
||||
|
@ -120,7 +121,7 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString);
|
||||
DEFINE_PROPERTY_REF_WITH_SETTER_AND_GETTER(PROP_ANIMATION_SETTINGS, AnimationSettings, animationSettings, QString);
|
||||
DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString);
|
||||
DEFINE_PROPERTY_REF(PROP_SIMULATOR_ID, SimulatorID, simulatorID, QUuid);
|
||||
DEFINE_PROPERTY_REF(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner);
|
||||
DEFINE_PROPERTY_REF(PROP_TEXT, Text, text, QString);
|
||||
DEFINE_PROPERTY(PROP_LINE_HEIGHT, LineHeight, lineHeight, float);
|
||||
DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, xColor);
|
||||
|
@ -152,7 +153,8 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString);
|
||||
DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString);
|
||||
DEFINE_PROPERTY(PROP_FACE_CAMERA, FaceCamera, faceCamera, bool);
|
||||
|
||||
DEFINE_PROPERTY_REF(PROP_ACTION_DATA, ActionData, actionData, QByteArray);
|
||||
|
||||
static QString getBackgroundModeString(BackgroundMode mode);
|
||||
|
||||
|
||||
|
@ -196,7 +198,7 @@ public:
|
|||
const QStringList& getTextureNames() const { return _textureNames; }
|
||||
void setTextureNames(const QStringList& value) { _textureNames = value; }
|
||||
|
||||
QString getSimulatorIDAsString() const { return _simulatorID.toString().mid(1,36).toUpper(); }
|
||||
QString getSimulatorIDAsString() const { return _simulationOwner.getID().toString().mid(1,36).toUpper(); }
|
||||
|
||||
void setVoxelDataDirty() { _voxelDataChanged = true; }
|
||||
|
||||
|
@ -205,6 +207,14 @@ public:
|
|||
void setCreated(QDateTime& v);
|
||||
|
||||
bool hasTerseUpdateChanges() const;
|
||||
bool hasMiscPhysicsChanges() const;
|
||||
|
||||
void clearSimulationOwner();
|
||||
void setSimulationOwner(const QUuid& id, uint8_t priority);
|
||||
void setSimulationOwner(const QByteArray& data);
|
||||
void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); }
|
||||
|
||||
void setActionDataDirty() { _actionDataChanged = true; }
|
||||
|
||||
private:
|
||||
QUuid _id;
|
||||
|
@ -284,7 +294,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Textures, textures, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, UserData, userData, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulatorID, simulatorID, QUuid());
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulationOwner, simulationOwner, SimulationOwner());
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Text, text, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LineHeight, lineHeight, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, TextColor, textColor, "");
|
||||
|
@ -304,7 +314,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Href, href, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Description, description, "");
|
||||
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ActionData, actionData, "");
|
||||
|
||||
properties.getStage().debugDump();
|
||||
properties.getAtmosphere().debugDump();
|
||||
properties.getSkybox().debugDump();
|
||||
|
|
|
@ -86,7 +86,7 @@ enum EntityPropertyList {
|
|||
|
||||
// available to all entities
|
||||
PROP_LOCKED,
|
||||
|
||||
|
||||
PROP_TEXTURES, // used by Model entities
|
||||
PROP_ANIMATION_SETTINGS, // used by Model entities
|
||||
PROP_USER_DATA, // all entities
|
||||
|
@ -100,11 +100,11 @@ enum EntityPropertyList {
|
|||
PROP_EMIT_STRENGTH,
|
||||
PROP_LOCAL_GRAVITY,
|
||||
PROP_PARTICLE_RADIUS,
|
||||
|
||||
|
||||
PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities
|
||||
PROP_MARKETPLACE_ID, // all entities
|
||||
PROP_ACCELERATION, // all entities
|
||||
PROP_SIMULATOR_ID, // all entities
|
||||
PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID
|
||||
PROP_NAME, // all entities
|
||||
PROP_COLLISION_SOUND_URL,
|
||||
PROP_RESTITUTION,
|
||||
|
@ -121,10 +121,11 @@ enum EntityPropertyList {
|
|||
// used by hyperlinks
|
||||
PROP_HREF,
|
||||
PROP_DESCRIPTION,
|
||||
|
||||
|
||||
PROP_FACE_CAMERA,
|
||||
PROP_SCRIPT_TIMESTAMP,
|
||||
|
||||
PROP_ACTION_DATA,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -9,18 +9,20 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntityScriptingInterface.h"
|
||||
|
||||
#include <VariantMapToScriptValue.h>
|
||||
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntityActionFactoryInterface.h"
|
||||
#include "EntityActionInterface.h"
|
||||
#include "EntitySimulation.h"
|
||||
#include "EntityTree.h"
|
||||
#include "LightEntityItem.h"
|
||||
#include "ModelEntityItem.h"
|
||||
#include "SimulationOwner.h"
|
||||
#include "ZoneEntityItem.h"
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntitySimulation.h"
|
||||
#include "EntityActionInterface.h"
|
||||
#include "EntityActionFactoryInterface.h"
|
||||
|
||||
#include "EntityScriptingInterface.h"
|
||||
|
||||
EntityScriptingInterface::EntityScriptingInterface() :
|
||||
_entityTree(NULL)
|
||||
|
@ -61,16 +63,6 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) {
|
|||
}
|
||||
}
|
||||
|
||||
void bidForSimulationOwnership(EntityItemProperties& properties) {
|
||||
// We make a bid for simulation ownership by declaring our sessionID as simulation owner
|
||||
// in the outgoing properties. The EntityServer may accept the bid or might not.
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
properties.setSimulatorID(myNodeID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
|
||||
|
||||
EntityItemProperties propertiesWithSimID = properties;
|
||||
|
@ -83,11 +75,15 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
_entityTree->lockForWrite();
|
||||
EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID);
|
||||
if (entity) {
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
// This Node is creating a new object. If it's in motion, set this Node as the simulator.
|
||||
bidForSimulationOwnership(propertiesWithSimID);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
|
||||
|
||||
// and make note of it now, so we can act on it right away.
|
||||
entity->setSimulatorID(propertiesWithSimID.getSimulatorID());
|
||||
entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
} else {
|
||||
qCDebug(entities) << "script failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
|
@ -113,7 +109,7 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
|
|||
if (entity) {
|
||||
results = entity->getProperties();
|
||||
|
||||
// TODO: improve sitting points and naturalDimensions in the future,
|
||||
// TODO: improve sitting points and naturalDimensions in the future,
|
||||
// for now we've included the old sitting points model behavior for entity types that are models
|
||||
// we've also added this hack for setting natural dimensions of models
|
||||
if (entity->getType() == EntityTypes::Model) {
|
||||
|
@ -146,23 +142,35 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties proper
|
|||
if (entity) {
|
||||
// make sure the properties has a type, so that the encode can know which properties to include
|
||||
properties.setType(entity->getType());
|
||||
if (properties.hasTerseUpdateChanges()) {
|
||||
bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges();
|
||||
bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges;
|
||||
if (hasPhysicsChanges) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
|
||||
|
||||
if (entity->getSimulatorID() == myNodeID) {
|
||||
// we think we already own the simulation, so make sure to send ALL TerseUpdate properties
|
||||
entity->getAllTerseUpdateProperties(properties);
|
||||
if (hasTerseUpdateChanges) {
|
||||
entity->getAllTerseUpdateProperties(properties);
|
||||
}
|
||||
// TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object
|
||||
// is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update
|
||||
// and instead let the physics simulation decide when to send a terse update. This would remove
|
||||
// the "slide-no-rotate" glitch (and typical a double-update) that we see during the "poke rolling
|
||||
// balls" test. However, even if we solve this problem we still need to provide a "slerp the visible
|
||||
// balls" test. However, even if we solve this problem we still need to provide a "slerp the visible
|
||||
// proxy toward the true physical position" feature to hide the final glitches in the remote watcher's
|
||||
// simulation.
|
||||
|
||||
if (entity->getSimulationPriority() < SCRIPT_EDIT_SIMULATION_PRIORITY) {
|
||||
// we re-assert our simulation ownership at a higher priority
|
||||
properties.setSimulationOwner(myNodeID,
|
||||
glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY));
|
||||
}
|
||||
} else {
|
||||
// we make a bid for simulation ownership
|
||||
properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
|
||||
entity->flagForOwnership();
|
||||
}
|
||||
// we make a bid for (or assert existing) simulation ownership
|
||||
properties.setSimulatorID(myNodeID);
|
||||
}
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
}
|
||||
|
@ -204,7 +212,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
|
|||
}
|
||||
|
||||
QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const {
|
||||
EntityItemID result;
|
||||
EntityItemID result;
|
||||
if (_entityTree) {
|
||||
_entityTree->lockForRead();
|
||||
EntityItemPointer closestEntity = _entityTree->findClosestEntity(center, radius);
|
||||
|
@ -264,8 +272,8 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlock
|
|||
return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking);
|
||||
}
|
||||
|
||||
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray,
|
||||
Octree::lockType lockType,
|
||||
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray,
|
||||
Octree::lockType lockType,
|
||||
bool precisionPicking) {
|
||||
|
||||
|
||||
|
@ -273,8 +281,8 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke
|
|||
if (_entityTree) {
|
||||
OctreeElement* element;
|
||||
EntityItemPointer intersectedEntity = NULL;
|
||||
result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face,
|
||||
(void**)&intersectedEntity, lockType, &result.accurate,
|
||||
result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face,
|
||||
(void**)&intersectedEntity, lockType, &result.accurate,
|
||||
precisionPicking);
|
||||
if (result.intersects && intersectedEntity) {
|
||||
result.entityID = intersectedEntity->getEntityItemID();
|
||||
|
@ -318,15 +326,15 @@ bool EntityScriptingInterface::getSendPhysicsUpdates() const {
|
|||
}
|
||||
|
||||
|
||||
RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
|
||||
intersects(false),
|
||||
RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
|
||||
intersects(false),
|
||||
accurate(true), // assume it's accurate
|
||||
entityID(),
|
||||
properties(),
|
||||
distance(0),
|
||||
face(),
|
||||
entity(NULL)
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) {
|
||||
|
@ -341,7 +349,7 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c
|
|||
|
||||
obj.setProperty("distance", value.distance);
|
||||
|
||||
QString faceName = "";
|
||||
QString faceName = "";
|
||||
// handle BoxFace
|
||||
switch (value.face) {
|
||||
case MIN_X_FACE:
|
||||
|
@ -446,35 +454,35 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line
|
|||
if (!_entityTree) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
EntityItemPointer entity = static_cast<EntityItemPointer>(_entityTree->findEntityByEntityItemID(entityID));
|
||||
if (!entity) {
|
||||
qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID;
|
||||
}
|
||||
|
||||
|
||||
EntityTypes::EntityType entityType = entity->getType();
|
||||
|
||||
|
||||
if (entityType != EntityTypes::Line) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
auto now = usecTimestampNow();
|
||||
|
||||
|
||||
LineEntityItem* lineEntity = static_cast<LineEntityItem*>(entity.get());
|
||||
_entityTree->lockForWrite();
|
||||
bool success = actor(*lineEntity);
|
||||
entity->setLastEdited(now);
|
||||
entity->setLastBroadcast(now);
|
||||
_entityTree->unlock();
|
||||
|
||||
|
||||
_entityTree->lockForRead();
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
_entityTree->unlock();
|
||||
|
||||
|
||||
properties.setLinePointsDirty();
|
||||
properties.setLastEdited(now);
|
||||
|
||||
|
||||
|
||||
|
||||
queueEntityMessage(PacketTypeEntityEdit, entityID, properties);
|
||||
return success;
|
||||
}
|
||||
|
@ -509,7 +517,6 @@ bool EntityScriptingInterface::appendPoint(QUuid entityID, const glm::vec3& poin
|
|||
{
|
||||
return lineEntity.appendPoint(point);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -537,6 +544,16 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
|
|||
|
||||
bool success = actor(simulation, entity);
|
||||
_entityTree->unlock();
|
||||
|
||||
// transmit the change
|
||||
_entityTree->lockForRead();
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
_entityTree->unlock();
|
||||
properties.setActionDataDirty();
|
||||
auto now = usecTimestampNow();
|
||||
properties.setLastEdited(now);
|
||||
queueEntityMessage(PacketTypeEntityEdit, entityID, properties);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -557,7 +574,14 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
|
|||
if (actionType == ACTION_TYPE_NONE) {
|
||||
return false;
|
||||
}
|
||||
if (actionFactory->factory(simulation, actionType, actionID, entity, arguments)) {
|
||||
EntityActionPointer action = actionFactory->factory(simulation, actionType, actionID, entity, arguments);
|
||||
if (action) {
|
||||
entity->addAction(simulation, action);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
if (entity->getSimulatorID() != myNodeID) {
|
||||
entity->flagForOwnership();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -571,13 +595,39 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
|
|||
|
||||
bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments) {
|
||||
return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) {
|
||||
return entity->updateAction(simulation, actionID, arguments);
|
||||
bool success = entity->updateAction(simulation, actionID, arguments);
|
||||
if (success) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
if (entity->getSimulatorID() != myNodeID) {
|
||||
entity->flagForOwnership();
|
||||
}
|
||||
}
|
||||
return success;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) {
|
||||
return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) {
|
||||
return entity->removeAction(simulation, actionID);
|
||||
});
|
||||
}
|
||||
|
||||
QVector<QUuid> EntityScriptingInterface::getActionIDs(const QUuid& entityID) {
|
||||
QVector<QUuid> result;
|
||||
actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) {
|
||||
QList<QUuid> actionIDs = entity->getActionIDs();
|
||||
result = QVector<QUuid>::fromList(actionIDs);
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID, const QUuid& actionID) {
|
||||
QVariantMap result;
|
||||
actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) {
|
||||
result = entity->getActionArguments(actionID);
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ public slots:
|
|||
Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value);
|
||||
Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value);
|
||||
Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value);
|
||||
|
||||
|
||||
Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector<glm::vec3>& points);
|
||||
Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point);
|
||||
|
||||
|
@ -131,6 +131,8 @@ public slots:
|
|||
Q_INVOKABLE QUuid addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments);
|
||||
Q_INVOKABLE bool updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments);
|
||||
Q_INVOKABLE bool deleteAction(const QUuid& entityID, const QUuid& actionID);
|
||||
Q_INVOKABLE QVector<QUuid> getActionIDs(const QUuid& entityID);
|
||||
Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID);
|
||||
|
||||
signals:
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
@ -165,7 +167,7 @@ private:
|
|||
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);
|
||||
|
||||
/// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode
|
||||
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType,
|
||||
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType,
|
||||
bool precisionPicking);
|
||||
|
||||
EntityTree* _entityTree;
|
||||
|
|
|
@ -260,3 +260,28 @@ void EntitySimulation::moveSimpleKinematics(const quint64& now) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntitySimulation::addAction(EntityActionPointer action) {
|
||||
lock();
|
||||
_actionsToAdd += action;
|
||||
unlock();
|
||||
}
|
||||
|
||||
void EntitySimulation::removeAction(const QUuid actionID) {
|
||||
lock();
|
||||
_actionsToRemove += actionID;
|
||||
unlock();
|
||||
}
|
||||
|
||||
void EntitySimulation::removeActions(QList<QUuid> actionIDsToRemove) {
|
||||
lock();
|
||||
_actionsToRemove += actionIDsToRemove;
|
||||
unlock();
|
||||
}
|
||||
|
||||
void EntitySimulation::applyActionChanges() {
|
||||
lock();
|
||||
_actionsToAdd.clear();
|
||||
_actionsToRemove.clear();
|
||||
unlock();
|
||||
}
|
||||
|
|
|
@ -57,10 +57,10 @@ public:
|
|||
|
||||
friend class EntityTree;
|
||||
|
||||
virtual void addAction(EntityActionPointer action) { _actionsToAdd += action; }
|
||||
virtual void removeAction(const QUuid actionID) { _actionsToRemove += actionID; }
|
||||
virtual void removeActions(QList<QUuid> actionIDsToRemove) { _actionsToRemove += actionIDsToRemove; }
|
||||
virtual void applyActionChanges() { _actionsToAdd.clear(); _actionsToRemove.clear(); }
|
||||
virtual void addAction(EntityActionPointer action);
|
||||
virtual void removeAction(const QUuid actionID);
|
||||
virtual void removeActions(QList<QUuid> actionIDsToRemove);
|
||||
virtual void applyActionChanges();
|
||||
|
||||
protected: // these only called by the EntityTree?
|
||||
/// \param entity pointer to EntityItem to be added
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "QVariantGLM.h"
|
||||
#include "EntitiesLogging.h"
|
||||
#include "RecurseOctreeToMapOperator.h"
|
||||
#include "LogHandler.h"
|
||||
|
||||
|
||||
const quint64 SIMULATOR_CHANGE_LOCKOUT_PERIOD = (quint64)(0.2f * USECS_PER_SECOND);
|
||||
|
@ -150,23 +151,34 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
|||
} else {
|
||||
if (getIsServer()) {
|
||||
bool simulationBlocked = !entity->getSimulatorID().isNull();
|
||||
if (properties.simulatorIDChanged()) {
|
||||
QUuid submittedID = properties.getSimulatorID();
|
||||
if (properties.simulationOwnerChanged()) {
|
||||
QUuid submittedID = properties.getSimulationOwner().getID();
|
||||
// a legit interface will only submit their own ID or NULL:
|
||||
if (submittedID.isNull()) {
|
||||
if (entity->getSimulatorID() == senderID) {
|
||||
// We only allow the simulation owner to clear their own simulationID's.
|
||||
simulationBlocked = false;
|
||||
properties.clearSimulationOwner(); // clear everything
|
||||
}
|
||||
// else: We assume the sender really did believe it was the simulation owner when it sent
|
||||
} else if (submittedID == senderID) {
|
||||
// the sender is trying to take or continue ownership
|
||||
if (entity->getSimulatorID().isNull() || entity->getSimulatorID() == senderID) {
|
||||
if (entity->getSimulatorID().isNull()) {
|
||||
// the sender it taking ownership
|
||||
properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY);
|
||||
simulationBlocked = false;
|
||||
} else if (entity->getSimulatorID() == senderID) {
|
||||
// the sender is asserting ownership
|
||||
simulationBlocked = false;
|
||||
} else {
|
||||
// the sender is trying to steal ownership from another simulator
|
||||
// so we apply the ownership change filter
|
||||
if (usecTimestampNow() - entity->getSimulatorIDChangedTime() > SIMULATOR_CHANGE_LOCKOUT_PERIOD) {
|
||||
// so we apply the rules for ownership change:
|
||||
// (1) higher priority wins
|
||||
// (2) equal priority wins if ownership filter has expired except...
|
||||
uint8_t oldPriority = entity->getSimulationPriority();
|
||||
uint8_t newPriority = properties.getSimulationOwner().getPriority();
|
||||
if (newPriority > oldPriority ||
|
||||
(newPriority == oldPriority && properties.getSimulationOwner().hasExpired())) {
|
||||
simulationBlocked = false;
|
||||
}
|
||||
}
|
||||
|
@ -174,12 +186,17 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
|||
// the entire update is suspect --> ignore it
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
simulationBlocked = senderID != entity->getSimulatorID();
|
||||
}
|
||||
if (simulationBlocked) {
|
||||
// squash the physics-related changes.
|
||||
properties.setSimulatorIDChanged(false);
|
||||
// squash ownership and physics-related changes.
|
||||
properties.setSimulationOwnerChanged(false);
|
||||
properties.setPositionChanged(false);
|
||||
properties.setRotationChanged(false);
|
||||
properties.setVelocityChanged(false);
|
||||
properties.setAngularVelocityChanged(false);
|
||||
properties.setAccelerationChanged(false);
|
||||
}
|
||||
}
|
||||
// else client accepts what the server says
|
||||
|
@ -574,41 +591,61 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
|
|||
|
||||
case PacketTypeEntityAdd:
|
||||
case PacketTypeEntityEdit: {
|
||||
quint64 startDecode = 0, endDecode = 0;
|
||||
quint64 startLookup = 0, endLookup = 0;
|
||||
quint64 startUpdate = 0, endUpdate = 0;
|
||||
quint64 startCreate = 0, endCreate = 0;
|
||||
quint64 startLogging = 0, endLogging = 0;
|
||||
|
||||
_totalEditMessages++;
|
||||
|
||||
EntityItemID entityItemID;
|
||||
EntityItemProperties properties;
|
||||
startDecode = usecTimestampNow();
|
||||
bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength,
|
||||
processedBytes, entityItemID, properties);
|
||||
endDecode = usecTimestampNow();
|
||||
|
||||
// If we got a valid edit packet, then it could be a new entity or it could be an update to
|
||||
// an existing entity... handle appropriately
|
||||
if (validEditPacket) {
|
||||
// search for the entity by EntityItemID
|
||||
startLookup = usecTimestampNow();
|
||||
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
|
||||
endLookup = usecTimestampNow();
|
||||
if (existingEntity && packetType == PacketTypeEntityEdit) {
|
||||
// if the EntityItem exists, then update it
|
||||
startLogging = usecTimestampNow();
|
||||
if (wantEditLogging()) {
|
||||
qCDebug(entities) << "User [" << senderNode->getUUID() << "] editing entity. ID:" << entityItemID;
|
||||
qCDebug(entities) << " properties:" << properties;
|
||||
}
|
||||
endLogging = usecTimestampNow();
|
||||
|
||||
startUpdate = usecTimestampNow();
|
||||
updateEntity(entityItemID, properties, senderNode);
|
||||
existingEntity->markAsChangedOnServer();
|
||||
endUpdate = usecTimestampNow();
|
||||
_totalUpdates++;
|
||||
} else if (packetType == PacketTypeEntityAdd) {
|
||||
if (senderNode->getCanRez()) {
|
||||
// this is a new entity... assign a new entityID
|
||||
if (wantEditLogging()) {
|
||||
qCDebug(entities) << "User [" << senderNode->getUUID() << "] adding entity.";
|
||||
qCDebug(entities) << " properties:" << properties;
|
||||
}
|
||||
properties.setCreated(properties.getLastEdited());
|
||||
startCreate = usecTimestampNow();
|
||||
EntityItemPointer newEntity = addEntity(entityItemID, properties);
|
||||
endCreate = usecTimestampNow();
|
||||
_totalCreates++;
|
||||
if (newEntity) {
|
||||
newEntity->markAsChangedOnServer();
|
||||
notifyNewlyCreatedEntity(*newEntity, senderNode);
|
||||
|
||||
startLogging = usecTimestampNow();
|
||||
if (wantEditLogging()) {
|
||||
qCDebug(entities) << "User [" << senderNode->getUUID() << "] added entity. ID:"
|
||||
<< newEntity->getEntityItemID();
|
||||
qCDebug(entities) << " properties:" << properties;
|
||||
}
|
||||
endLogging = usecTimestampNow();
|
||||
|
||||
}
|
||||
} else {
|
||||
|
@ -616,9 +653,19 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
|
|||
<< "] attempted to add an entity.";
|
||||
}
|
||||
} else {
|
||||
static QString repeatedMessage =
|
||||
LogHandler::getInstance().addRepeatedMessageRegex("^Add or Edit failed.*");
|
||||
qCDebug(entities) << "Add or Edit failed." << packetType << existingEntity.get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_totalDecodeTime += endDecode - startDecode;
|
||||
_totalLookupTime += endLookup - startLookup;
|
||||
_totalUpdateTime += endUpdate - startUpdate;
|
||||
_totalCreateTime += endCreate - startCreate;
|
||||
_totalLoggingTime += endLogging - startLogging;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -168,6 +168,23 @@ public:
|
|||
|
||||
float getContentsLargestDimension();
|
||||
|
||||
virtual void resetEditStats() {
|
||||
_totalEditMessages = 0;
|
||||
_totalUpdates = 0;
|
||||
_totalCreates = 0;
|
||||
_totalDecodeTime = 0;
|
||||
_totalLookupTime = 0;
|
||||
_totalUpdateTime = 0;
|
||||
_totalCreateTime = 0;
|
||||
_totalLoggingTime = 0;
|
||||
}
|
||||
|
||||
virtual quint64 getAverageDecodeTime() const { return _totalEditMessages == 0 ? 0 : _totalDecodeTime / _totalEditMessages; }
|
||||
virtual quint64 getAverageLookupTime() const { return _totalEditMessages == 0 ? 0 : _totalLookupTime / _totalEditMessages; }
|
||||
virtual quint64 getAverageUpdateTime() const { return _totalUpdates == 0 ? 0 : _totalUpdateTime / _totalUpdates; }
|
||||
virtual quint64 getAverageCreateTime() const { return _totalCreates == 0 ? 0 : _totalCreateTime / _totalCreates; }
|
||||
virtual quint64 getAverageLoggingTime() const { return _totalEditMessages == 0 ? 0 : _totalLoggingTime / _totalEditMessages; }
|
||||
|
||||
signals:
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
void addingEntity(const EntityItemID& entityID);
|
||||
|
@ -202,6 +219,17 @@ private:
|
|||
|
||||
bool _wantEditLogging = false;
|
||||
void maybeNotifyNewCollisionSoundURL(const QString& oldCollisionSoundURL, const QString& newCollisionSoundURL);
|
||||
|
||||
|
||||
// some performance tracking properties - only used in server trees
|
||||
int _totalEditMessages = 0;
|
||||
int _totalUpdates = 0;
|
||||
int _totalCreates = 0;
|
||||
quint64 _totalDecodeTime = 0;
|
||||
quint64 _totalLookupTime = 0;
|
||||
quint64 _totalUpdateTime = 0;
|
||||
quint64 _totalCreateTime = 0;
|
||||
quint64 _totalLoggingTime = 0;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTree_h
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
class EntityItem;
|
||||
typedef std::shared_ptr<EntityItem> EntityItemPointer;
|
||||
typedef std::weak_ptr<EntityItem> EntityItemWeakPointer;
|
||||
|
||||
inline uint qHash(const EntityItemPointer& a, uint seed) {
|
||||
return qHash(a.get(), seed);
|
||||
|
|
|
@ -107,7 +107,7 @@ bool LineEntityItem::setLinePoints(const QVector<glm::vec3>& points) {
|
|||
}
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
glm::vec3 point = points.at(i);
|
||||
glm::vec3 pos = getPosition();
|
||||
// glm::vec3 pos = getPosition();
|
||||
glm::vec3 halfBox = getDimensions() * 0.5f;
|
||||
if ( (point.x < - halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < - halfBox.z || point.z > halfBox.z) ) {
|
||||
qDebug() << "Point is outside entity's bounding box";
|
||||
|
|
|
@ -23,18 +23,19 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
|||
// has finished simulating it.
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
SetOfEntities::iterator itemItr = _hasSimulationOwnerEntities.begin();
|
||||
while (itemItr != _hasSimulationOwnerEntities.end()) {
|
||||
SetOfEntities::iterator itemItr = _entitiesWithSimulator.begin();
|
||||
while (itemItr != _entitiesWithSimulator.end()) {
|
||||
EntityItemPointer entity = *itemItr;
|
||||
if (entity->getSimulatorID().isNull()) {
|
||||
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
|
||||
itemItr = _entitiesWithSimulator.erase(itemItr);
|
||||
} else if (now - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) {
|
||||
SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID());
|
||||
if (ownerNode.isNull() || !ownerNode->isAlive()) {
|
||||
qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID();
|
||||
// TODO: zero velocities when we clear simulatorID?
|
||||
entity->setSimulatorID(QUuid());
|
||||
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
|
||||
entity->clearSimulationOwnership();
|
||||
itemItr = _entitiesWithSimulator.erase(itemItr);
|
||||
// zero the velocity on this entity so that it doesn't drift far away
|
||||
entity->setVelocity(glm::vec3(0.0f));
|
||||
} else {
|
||||
++itemItr;
|
||||
}
|
||||
|
@ -47,23 +48,23 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
|||
void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
|
||||
EntitySimulation::addEntityInternal(entity);
|
||||
if (!entity->getSimulatorID().isNull()) {
|
||||
_hasSimulationOwnerEntities.insert(entity);
|
||||
_entitiesWithSimulator.insert(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
|
||||
_hasSimulationOwnerEntities.remove(entity);
|
||||
_entitiesWithSimulator.remove(entity);
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
|
||||
EntitySimulation::changeEntityInternal(entity);
|
||||
if (!entity->getSimulatorID().isNull()) {
|
||||
_hasSimulationOwnerEntities.insert(entity);
|
||||
_entitiesWithSimulator.insert(entity);
|
||||
}
|
||||
entity->clearDirtyFlags();
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::clearEntitiesInternal() {
|
||||
_hasSimulationOwnerEntities.clear();
|
||||
_entitiesWithSimulator.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ protected:
|
|||
virtual void changeEntityInternal(EntityItemPointer entity);
|
||||
virtual void clearEntitiesInternal();
|
||||
|
||||
SetOfEntities _hasSimulationOwnerEntities;
|
||||
SetOfEntities _entitiesWithSimulator;
|
||||
};
|
||||
|
||||
#endif // hifi_SimpleEntitySimulation_h
|
||||
|
|
181
libraries/entities/src/SimulationOwner.cpp
Normal file
181
libraries/entities/src/SimulationOwner.cpp
Normal file
|
@ -0,0 +1,181 @@
|
|||
//
|
||||
// SimulationOwner.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2015.06.19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "SimulationOwner.h"
|
||||
|
||||
#include <iostream> // included for tests
|
||||
#include <assert.h>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
// static
|
||||
const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1;
|
||||
|
||||
|
||||
SimulationOwner::SimulationOwner(const SimulationOwner& other)
|
||||
: _id(other._id), _priority(other._priority), _expiry(other._expiry) {
|
||||
}
|
||||
|
||||
QByteArray SimulationOwner::toByteArray() const {
|
||||
QByteArray data = _id.toRfc4122();
|
||||
data.append(_priority);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool SimulationOwner::fromByteArray(const QByteArray& data) {
|
||||
if (data.size() == NUM_BYTES_ENCODED) {
|
||||
QByteArray idBytes = data.left(NUM_BYTES_RFC4122_UUID);
|
||||
_id = QUuid::fromRfc4122(idBytes);
|
||||
_priority = data[NUM_BYTES_RFC4122_UUID];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SimulationOwner::clear() {
|
||||
_id = QUuid();
|
||||
_priority = 0;
|
||||
_expiry = 0;
|
||||
}
|
||||
|
||||
void SimulationOwner::setPriority(quint8 priority) {
|
||||
_priority = priority;
|
||||
if (_priority == 0) {
|
||||
// when priority is zero we clear everything
|
||||
_expiry = 0;
|
||||
_id = QUuid();
|
||||
}
|
||||
}
|
||||
|
||||
void SimulationOwner::promotePriority(quint8 priority) {
|
||||
if (priority > _priority) {
|
||||
_priority = priority;
|
||||
updateExpiry();
|
||||
}
|
||||
}
|
||||
|
||||
bool SimulationOwner::setID(const QUuid& id) {
|
||||
if (_id != id) {
|
||||
_id = id;
|
||||
updateExpiry();
|
||||
if (_id.isNull()) {
|
||||
_priority = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimulationOwner::set(const QUuid& id, quint8 priority) {
|
||||
setPriority(priority);
|
||||
return setID(id);
|
||||
}
|
||||
|
||||
bool SimulationOwner::set(const SimulationOwner& owner) {
|
||||
setPriority(owner._priority);
|
||||
return setID(owner._id);
|
||||
}
|
||||
|
||||
void SimulationOwner::updateExpiry() {
|
||||
const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5;
|
||||
_expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY;
|
||||
}
|
||||
|
||||
// NOTE: eventually this code will be moved into unit tests
|
||||
// static debug
|
||||
void SimulationOwner::test() {
|
||||
{ // test default constructor
|
||||
SimulationOwner simOwner;
|
||||
if (!simOwner.isNull()) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should be NULL" << std::endl;
|
||||
}
|
||||
|
||||
if (simOwner.getPriority() != 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
{ // test set constructor
|
||||
QUuid id = QUuid::createUuid();
|
||||
quint8 priority = 128;
|
||||
SimulationOwner simOwner(id, priority);
|
||||
if (simOwner.isNull()) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl;
|
||||
}
|
||||
|
||||
if (simOwner.getID() != id) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl;
|
||||
}
|
||||
|
||||
if (simOwner.getPriority() != priority) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl;
|
||||
}
|
||||
|
||||
QUuid otherID = QUuid::createUuid();
|
||||
if (simOwner.getID() == otherID) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
{ // test set()
|
||||
QUuid id = QUuid::createUuid();
|
||||
quint8 priority = 1;
|
||||
SimulationOwner simOwner;
|
||||
simOwner.set(id, priority);
|
||||
if (simOwner.isNull()) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl;
|
||||
}
|
||||
|
||||
if (simOwner.getID() != id) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl;
|
||||
}
|
||||
|
||||
if (simOwner.getPriority() != priority) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
{ // test encode/decode
|
||||
SimulationOwner ownerA(QUuid::createUuid(), 1);
|
||||
SimulationOwner ownerB(QUuid::createUuid(), 2);
|
||||
|
||||
QByteArray data = ownerA.toByteArray();
|
||||
ownerB.fromByteArray(data);
|
||||
|
||||
if (ownerA.getID() != ownerB.getID()) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : ownerA._id should be equal to ownerB._id" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SimulationOwner::operator!=(const SimulationOwner& other) {
|
||||
return (_id != other._id && _priority != other._priority);
|
||||
}
|
||||
|
||||
SimulationOwner& SimulationOwner::operator=(const SimulationOwner& other) {
|
||||
_priority = other._priority;
|
||||
if (_priority == 0) {
|
||||
_id = QUuid();
|
||||
_expiry = 0;
|
||||
} else {
|
||||
if (_id != other._id) {
|
||||
updateExpiry();
|
||||
}
|
||||
_id = other._id;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& d, const SimulationOwner& simOwner) {
|
||||
d << "{ id : " << simOwner._id << ", priority : " << (int)simOwner._priority << " }";
|
||||
return d;
|
||||
}
|
||||
|
88
libraries/entities/src/SimulationOwner.h
Normal file
88
libraries/entities/src/SimulationOwner.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// SimulationOwner.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2015.06.19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_SimulationOwner_h
|
||||
#define hifi_SimulationOwner_h
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
|
||||
const quint8 NO_PRORITY = 0x00;
|
||||
|
||||
// Simulation observers will bid to simulate unowned active objects at the lowest possible priority
|
||||
// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it
|
||||
// to RECRUIT priority so that other volunteers don't accidentally take over.
|
||||
const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01;
|
||||
const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
|
||||
|
||||
// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
|
||||
const quint8 SCRIPT_EDIT_SIMULATION_PRIORITY = 0x80;
|
||||
|
||||
// PERSONAL priority (needs a better name) is the level at which a simulation observer will bid for
|
||||
// objects that collide its MyAvatar.
|
||||
const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_EDIT_SIMULATION_PRIORITY - 1;
|
||||
|
||||
|
||||
class SimulationOwner {
|
||||
public:
|
||||
static const int NUM_BYTES_ENCODED;
|
||||
|
||||
SimulationOwner() : _id(), _priority(0), _expiry(0) {}
|
||||
SimulationOwner(const QUuid& id, quint8 priority) : _id(id), _priority(priority), _expiry(0) {}
|
||||
SimulationOwner(const SimulationOwner& other);
|
||||
|
||||
const QUuid& getID() const { return _id; }
|
||||
quint8 getPriority() const { return _priority; }
|
||||
const quint64& getExpiry() const { return _expiry; }
|
||||
|
||||
QByteArray toByteArray() const;
|
||||
bool fromByteArray(const QByteArray& data);
|
||||
|
||||
void clear();
|
||||
|
||||
void setPriority(quint8 priority);
|
||||
void promotePriority(quint8 priority);
|
||||
|
||||
// return true if id is changed
|
||||
bool setID(const QUuid& id);
|
||||
bool set(const QUuid& id, quint8 priority);
|
||||
bool set(const SimulationOwner& owner);
|
||||
|
||||
bool isNull() const { return _id.isNull(); }
|
||||
bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); }
|
||||
|
||||
void updateExpiry();
|
||||
|
||||
bool hasExpired() const { return usecTimestampNow() > _expiry; }
|
||||
|
||||
bool operator>=(quint8 priority) const { return _priority >= priority; }
|
||||
bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); }
|
||||
|
||||
bool operator!=(const SimulationOwner& other);
|
||||
SimulationOwner& operator=(const SimulationOwner& other);
|
||||
|
||||
friend QDebug& operator<<(QDebug& d, const SimulationOwner& simOwner);
|
||||
|
||||
// debug
|
||||
static void test();
|
||||
|
||||
private:
|
||||
QUuid _id;
|
||||
quint8 _priority;
|
||||
quint64 _expiry;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // hifi_SimulationOwner_h
|
|
@ -160,6 +160,10 @@ void GLBackend::do_setUniformBuffer(Batch& batch, uint32 paramOffset) {
|
|||
GLuint bo = getBufferID(*uniformBuffer);
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, slot, bo, rangeStart, rangeSize);
|
||||
#else
|
||||
// because we rely on the program uniform mechanism we need to have
|
||||
// the program bound, thank you MacOSX Legacy profile.
|
||||
updatePipeline();
|
||||
|
||||
GLfloat* data = (GLfloat*) (uniformBuffer->getData() + rangeStart);
|
||||
glUniform4fv(slot, rangeSize / sizeof(GLfloat[4]), data);
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ public:
|
|||
uint8 _function = LESS;
|
||||
uint8 _writeMask = true;
|
||||
uint8 _enabled = false;
|
||||
uint8 _spare;
|
||||
uint8 _spare = 0;
|
||||
public:
|
||||
DepthTest(bool enabled = false, bool writeMask = true, ComparisonFunction func = LESS) :
|
||||
_function(func), _writeMask(writeMask), _enabled(enabled) {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// JSONBreakableMarshal.cpp
|
||||
// JSONBreakableMarshal.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 04/28/15.
|
||||
|
@ -25,26 +25,26 @@ QStringList JSONBreakableMarshal::toStringList(const QJsonValue& jsonValue, cons
|
|||
if (jsonValue.isObject()) {
|
||||
|
||||
QJsonObject jsonObject = jsonValue.toObject();
|
||||
|
||||
|
||||
// enumerate the keys of the QJsonObject
|
||||
foreach(const QString& key, jsonObject.keys()) {
|
||||
QJsonValue childValue = jsonObject[key];
|
||||
|
||||
// setup the keypath for this key
|
||||
QString valueKeypath = (keypath.isEmpty() ? "" : keypath + ".") + key;
|
||||
|
||||
|
||||
if (childValue.isObject() || childValue.isArray()) {
|
||||
// recursion is required since the value is a QJsonObject or QJsonArray
|
||||
result << toStringList(childValue, valueKeypath);
|
||||
} else {
|
||||
// no recursion required, call our toString method to get the string representation
|
||||
// append the QStringList resulting from that to our QStringList
|
||||
result << toString(childValue, valueKeypath);
|
||||
result << toString(childValue, valueKeypath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (jsonValue.isArray()) {
|
||||
QJsonArray jsonArray = jsonValue.toArray();
|
||||
|
||||
|
||||
// enumerate the elements in this QJsonArray
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
QJsonValue arrayValue = jsonArray[i];
|
||||
|
@ -77,8 +77,8 @@ const QString JSON_UNKNOWN_AS_STRING = "unknown";
|
|||
QString JSONBreakableMarshal::toString(const QJsonValue& jsonValue, const QString& keypath) {
|
||||
// default the value as a string to unknown in case conversion fails
|
||||
QString valueAsString = JSON_UNKNOWN_AS_STRING;
|
||||
|
||||
// as the QJsonValue what type it is and format its value as a string accordingly
|
||||
|
||||
// ask the QJsonValue what type it is and format its value as a string accordingly
|
||||
if (jsonValue.isNull()) {
|
||||
valueAsString = JSON_NULL_AS_STRING;
|
||||
} else if (jsonValue.isBool()) {
|
||||
|
@ -90,7 +90,7 @@ QString JSONBreakableMarshal::toString(const QJsonValue& jsonValue, const QStrin
|
|||
} else if (jsonValue.isUndefined()) {
|
||||
valueAsString = JSON_UNDEFINED_AS_STRING;
|
||||
} else if (jsonValue.isArray() || jsonValue.isObject()) {
|
||||
qDebug() << "JSONBreakableMarshal::toString does not handle conversion of a QJsonObject or QJsonArray."
|
||||
qDebug() << "JSONBreakableMarshal::toString does not handle conversion of a QJsonObject or QJsonArray."
|
||||
<< "You should call JSONBreakableMarshal::toStringList instead.";
|
||||
} else {
|
||||
qDebug() << "Unrecognized QJsonValue - JSONBreakableMarshal cannot convert to string.";
|
||||
|
@ -102,14 +102,14 @@ QString JSONBreakableMarshal::toString(const QJsonValue& jsonValue, const QStrin
|
|||
QVariant JSONBreakableMarshal::fromString(const QString& marshalValue) {
|
||||
// default the value to null
|
||||
QVariant result;
|
||||
|
||||
|
||||
// attempt to match the value with our expected strings
|
||||
if (marshalValue == JSON_NULL_AS_STRING) {
|
||||
// this is already our default, we don't need to do anything here
|
||||
} else if (marshalValue == JSON_TRUE_AS_STRING || marshalValue == JSON_FALSE_AS_STRING) {
|
||||
result = QVariant(marshalValue == JSON_TRUE_AS_STRING ? true : false);
|
||||
} else if (marshalValue == JSON_UNDEFINED_AS_STRING) {
|
||||
result = JSON_UNDEFINED_AS_STRING;
|
||||
result = JSON_UNDEFINED_AS_STRING;
|
||||
} else if (marshalValue == JSON_UNKNOWN_AS_STRING) {
|
||||
// we weren't able to marshal this value at the other end, set it as our unknown string
|
||||
result = JSON_UNKNOWN_AS_STRING;
|
||||
|
@ -125,14 +125,15 @@ QVariant JSONBreakableMarshal::fromString(const QString& marshalValue) {
|
|||
// use a regex to look for surrounding quotes first
|
||||
const QString JSON_STRING_REGEX = "^\"([\\s\\S]*)\"$";
|
||||
QRegExp stringRegex(JSON_STRING_REGEX);
|
||||
|
||||
|
||||
if (stringRegex.indexIn(marshalValue) != -1) {
|
||||
// set the result to the string value
|
||||
result = stringRegex.cap(1);
|
||||
} else {
|
||||
// we failed to convert the value to anything, set the result to our unknown value
|
||||
qDebug() << "Unrecognized output from JSONBreakableMarshal - could not convert" << marshalValue << "to QVariant.";
|
||||
result = JSON_UNKNOWN_AS_STRING;
|
||||
// we failed to convert the value to anything, set the result to our unknown value
|
||||
qDebug() << "Unrecognized output from JSONBreakableMarshal - could not convert"
|
||||
<< marshalValue << "to QVariant.";
|
||||
result = JSON_UNKNOWN_AS_STRING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,14 +145,14 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
QVariant result = QVariantMap();
|
||||
|
||||
foreach(const QString& marshalString, stringList) {
|
||||
|
||||
|
||||
// find the equality operator
|
||||
int equalityIndex = marshalString.indexOf('=');
|
||||
|
||||
|
||||
// bail on parsing if we didn't find the equality operator
|
||||
if (equalityIndex != -1) {
|
||||
|
||||
QVariant* currentValue = &result;
|
||||
|
||||
QVariant* currentValue = &result;
|
||||
|
||||
// pull the key (everything left of the equality sign)
|
||||
QString parentKeypath;
|
||||
|
@ -160,7 +161,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
// setup for array index checking
|
||||
const QString ARRAY_INDEX_REGEX_STRING = "\\[(\\d+)\\]";
|
||||
QRegExp arrayRegex(ARRAY_INDEX_REGEX_STRING);
|
||||
|
||||
|
||||
// as long as we have a keypath we need to recurse downwards
|
||||
while (!keypath.isEmpty()) {
|
||||
parentKeypath = keypath;
|
||||
|
@ -176,7 +177,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
}
|
||||
|
||||
QVariantList& currentList = *static_cast<QVariantList*>(currentValue->data());
|
||||
|
||||
|
||||
// figure out what index we want to get the QJsonValue& for
|
||||
bool didConvert = false;
|
||||
int arrayIndex = arrayRegex.cap(1).toInt(&didConvert);
|
||||
|
@ -187,7 +188,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
|
||||
for (int i = currentList.size(); i < arrayIndex + 1; i++) {
|
||||
// add the null QJsonValue at this array index to get the array to the right size
|
||||
currentList.push_back(QJsonValue());
|
||||
currentList.push_back(QJsonValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,7 +197,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
|
||||
// update the keypath by bumping past the array index
|
||||
keypath = keypath.mid(keypath.indexOf(']') + 1);
|
||||
|
||||
|
||||
// check if there is a key after the array index - if so push the keypath forward by a char
|
||||
if (keypath.startsWith(".")) {
|
||||
keypath = keypath.mid(1);
|
||||
|
@ -209,7 +210,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
|
||||
} else {
|
||||
int keySeparatorIndex = keypath.indexOf('.');
|
||||
|
||||
|
||||
// we need to figure out what the key to look at is
|
||||
QString subKey = keypath;
|
||||
|
||||
|
@ -219,20 +220,20 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
|
||||
if (arrayBracketIndex == -1 || (keySeparatorIndex != -1 && keySeparatorIndex < arrayBracketIndex)) {
|
||||
nextBreakIndex = keySeparatorIndex;
|
||||
nextKeypathStartIndex = keySeparatorIndex + 1;
|
||||
} else if (keySeparatorIndex == -1 || (arrayBracketIndex != -1
|
||||
nextKeypathStartIndex = keySeparatorIndex + 1;
|
||||
} else if (keySeparatorIndex == -1 || (arrayBracketIndex != -1
|
||||
&& arrayBracketIndex < keySeparatorIndex)) {
|
||||
nextBreakIndex = arrayBracketIndex;
|
||||
nextKeypathStartIndex = arrayBracketIndex;
|
||||
} else {
|
||||
qDebug() << "Unrecognized key format while trying to parse " << keypath << " - will not add"
|
||||
qDebug() << "Unrecognized key format while trying to parse " << keypath << " - will not add"
|
||||
<< "value to resulting QJsonObject.";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// set the current key from the determined index
|
||||
subKey = keypath.left(nextBreakIndex);
|
||||
|
||||
|
||||
// update the keypath being processed
|
||||
keypath = keypath.mid(nextKeypathStartIndex);
|
||||
|
||||
|
@ -244,11 +245,11 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
|
||||
// we're here becuase we know the current value should be an object
|
||||
// if it isn't then make it one
|
||||
|
||||
|
||||
if (!currentValue->canConvert(QMetaType::QVariantMap) || currentValue->isNull()) {
|
||||
*currentValue = QVariantMap();
|
||||
}
|
||||
|
||||
|
||||
QVariantMap& currentMap = *static_cast<QVariantMap*>(currentValue->data());
|
||||
|
||||
// is there a QJsonObject for this key yet?
|
||||
|
@ -256,19 +257,19 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList)
|
|||
if (!currentMap.contains(subKey)) {
|
||||
currentMap[subKey] = QVariant();
|
||||
}
|
||||
|
||||
|
||||
// change the currentValue to the QJsonValue for this key
|
||||
currentValue = ¤tMap[subKey];
|
||||
}
|
||||
}
|
||||
|
||||
*currentValue = fromString(marshalString.mid(equalityIndex + 1));
|
||||
|
||||
|
||||
if (_interpolationMap.contains(parentKeypath)) {
|
||||
// we expect the currentValue here to be a string, that's the key we use for interpolation
|
||||
// bail if it isn't
|
||||
if (currentValue->canConvert(QMetaType::QString)
|
||||
&& _interpolationMap[parentKeypath].canConvert(QMetaType::QVariantMap)) {
|
||||
if (currentValue->canConvert(QMetaType::QString)
|
||||
&& _interpolationMap[parentKeypath].canConvert(QMetaType::QVariantMap)) {
|
||||
*currentValue = _interpolationMap[parentKeypath].toMap()[currentValue->toString()];
|
||||
}
|
||||
}
|
||||
|
@ -283,7 +284,7 @@ QVariantMap JSONBreakableMarshal::fromStringBuffer(const QByteArray& buffer) {
|
|||
QStringList packetList;
|
||||
int currentIndex = 0;
|
||||
int currentSeparator = buffer.indexOf('\0');
|
||||
|
||||
|
||||
while (currentIndex < buffer.size() - 1) {
|
||||
packetList << QString::fromUtf8(buffer.mid(currentIndex, currentSeparator));
|
||||
|
||||
|
@ -291,13 +292,13 @@ QVariantMap JSONBreakableMarshal::fromStringBuffer(const QByteArray& buffer) {
|
|||
// no more separators to be found, break out of here so we're not looping for nothing
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// bump the currentIndex up to the last found separator
|
||||
currentIndex = currentSeparator + 1;
|
||||
|
||||
// find the index of the next separator, assuming this one wasn't the last one in the packet
|
||||
if (currentSeparator < buffer.size() - 1) {
|
||||
currentSeparator = buffer.indexOf('\0', currentIndex);
|
||||
currentSeparator = buffer.indexOf('\0', currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,30 +306,30 @@ QVariantMap JSONBreakableMarshal::fromStringBuffer(const QByteArray& buffer) {
|
|||
return fromStringList(packetList);
|
||||
}
|
||||
|
||||
void JSONBreakableMarshal::addInterpolationForKey(const QString& rootKey, const QString& interpolationKey,
|
||||
void JSONBreakableMarshal::addInterpolationForKey(const QString& rootKey, const QString& interpolationKey,
|
||||
const QJsonValue& interpolationValue) {
|
||||
// if there is no map already beneath this key in our _interpolationMap create a QVariantMap there now
|
||||
|
||||
|
||||
if (!_interpolationMap.contains(rootKey)) {
|
||||
_interpolationMap.insert(rootKey, QVariantMap());
|
||||
}
|
||||
|
||||
|
||||
if (_interpolationMap[rootKey].canConvert(QMetaType::QVariantMap)) {
|
||||
QVariantMap& mapForRootKey = *static_cast<QVariantMap*>(_interpolationMap[rootKey].data());
|
||||
|
||||
|
||||
mapForRootKey.insert(interpolationKey, QVariant(interpolationValue));
|
||||
} else {
|
||||
qDebug() << "JSONBreakableMarshal::addInterpolationForKey could not convert variant at key" << rootKey
|
||||
qDebug() << "JSONBreakableMarshal::addInterpolationForKey could not convert variant at key" << rootKey
|
||||
<< "to a QVariantMap. Can not add interpolation.";
|
||||
}
|
||||
}
|
||||
|
||||
void JSONBreakableMarshal::removeInterpolationForKey(const QString& rootKey, const QString& interpolationKey) {
|
||||
// make sure the interpolation map contains this root key and that the value is a map
|
||||
|
||||
|
||||
if (_interpolationMap.contains(rootKey)) {
|
||||
QVariant& rootValue = _interpolationMap[rootKey];
|
||||
|
||||
|
||||
if (!rootValue.isNull() && rootValue.canConvert(QMetaType::QVariantMap)) {
|
||||
// remove the value at the interpolationKey
|
||||
static_cast<QVariantMap*>(rootValue.data())->remove(interpolationKey);
|
||||
|
|
|
@ -48,6 +48,8 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
|
|||
_localSockAddr(),
|
||||
_publicSockAddr(),
|
||||
_stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT),
|
||||
_numCollectedPackets(0),
|
||||
_numCollectedBytes(0),
|
||||
_packetStatTimer(),
|
||||
_thisNodeCanAdjustLocks(false),
|
||||
_thisNodeCanRez(true)
|
||||
|
|
22
libraries/networking/src/Packet.cpp
Normal file
22
libraries/networking/src/Packet.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// Packet.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/2/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Packet.h"
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
std::unique_ptr<Packet> Packet::makePacket() {
|
||||
return std::unique_ptr<Packet>(new Packet());
|
||||
}
|
||||
|
||||
QByteArray Packet::payload() {
|
||||
return QByteArray();
|
||||
}
|
31
libraries/networking/src/Packet.h
Normal file
31
libraries/networking/src/Packet.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// Packet.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/2/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Packet_h
|
||||
#define hifi_Packet_h
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QByteArray;
|
||||
|
||||
class Packet {
|
||||
std::unique_ptr<Packet> makePacket();
|
||||
QByteArray payload();
|
||||
|
||||
protected:
|
||||
Packet();
|
||||
Packet(const Packet&) = delete;
|
||||
Packet(Packet&&) = delete;
|
||||
Packet& operator=(const Packet&) = delete;
|
||||
Packet& operator=(Packet&&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_Packet_h
|
|
@ -73,7 +73,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketTypeEntityAdd:
|
||||
case PacketTypeEntityEdit:
|
||||
case PacketTypeEntityData:
|
||||
return VERSION_ENTITIES_SCRIPT_TIMESTAMP_FIX;
|
||||
return VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE;
|
||||
case PacketTypeEntityErase:
|
||||
return 2;
|
||||
case PacketTypeAudioStreamStats:
|
||||
|
|
|
@ -188,5 +188,6 @@ const PacketVersion VERSION_ENTITIES_LINE_POINTS = 29;
|
|||
const PacketVersion VERSION_ENTITIES_FACE_CAMERA = 30;
|
||||
const PacketVersion VERSION_ENTITIES_SCRIPT_TIMESTAMP = 31;
|
||||
const PacketVersion VERSION_ENTITIES_SCRIPT_TIMESTAMP_FIX = 32;
|
||||
const PacketVersion VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE = 33;
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -38,19 +38,28 @@ bool ReceivedPacketProcessor::process() {
|
|||
_hasPackets.wait(&_waitingOnPacketsMutex, getMaxWait());
|
||||
_waitingOnPacketsMutex.unlock();
|
||||
}
|
||||
|
||||
preProcess();
|
||||
while (_packets.size() > 0) {
|
||||
lock(); // lock to make sure nothing changes on us
|
||||
NetworkPacket& packet = _packets.front(); // get the oldest packet
|
||||
NetworkPacket temporary = packet; // make a copy of the packet in case the vector is resized on us
|
||||
_packets.erase(_packets.begin()); // remove the oldest packet
|
||||
if (!temporary.getNode().isNull()) {
|
||||
_nodePacketCounts[temporary.getNode()->getUUID()]--;
|
||||
}
|
||||
unlock(); // let others add to the packets
|
||||
processPacket(temporary.getNode(), temporary.getByteArray()); // process our temporary copy
|
||||
if (!_packets.size()) {
|
||||
return isStillRunning();
|
||||
}
|
||||
|
||||
lock();
|
||||
QVector<NetworkPacket> currentPackets;
|
||||
currentPackets.swap(_packets);
|
||||
unlock();
|
||||
|
||||
foreach(auto& packet, currentPackets) {
|
||||
processPacket(packet.getNode(), packet.getByteArray());
|
||||
midProcess();
|
||||
}
|
||||
|
||||
lock();
|
||||
foreach(auto& packet, currentPackets) {
|
||||
_nodePacketCounts[packet.getNode()->getUUID()]--;
|
||||
}
|
||||
unlock();
|
||||
|
||||
postProcess();
|
||||
return isStillRunning(); // keep running till they terminate us
|
||||
}
|
||||
|
|
|
@ -367,8 +367,16 @@ public:
|
|||
bool getIsClient() const { return !_isServer; } /// Is this a client based tree. Allows guards for certain operations
|
||||
void setIsClient(bool isClient) { _isServer = !isClient; }
|
||||
|
||||
virtual void dumpTree() { };
|
||||
virtual void pruneTree() { };
|
||||
virtual void dumpTree() { }
|
||||
virtual void pruneTree() { }
|
||||
|
||||
virtual void resetEditStats() { }
|
||||
virtual quint64 getAverageDecodeTime() const { return 0; }
|
||||
virtual quint64 getAverageLookupTime() const { return 0; }
|
||||
virtual quint64 getAverageUpdateTime() const { return 0; }
|
||||
virtual quint64 getAverageCreateTime() const { return 0; }
|
||||
virtual quint64 getAverageLoggingTime() const { return 0; }
|
||||
|
||||
|
||||
signals:
|
||||
void importSize(float x, float y, float z);
|
||||
|
|
|
@ -247,7 +247,6 @@ public:
|
|||
static int unpackDataFromBytes(const unsigned char* dataBytes, QVector<glm::vec3>& result);
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result);
|
||||
|
||||
|
||||
private:
|
||||
/// appends raw bytes, might fail if byte would cause packet to be too large
|
||||
bool append(const unsigned char* data, int length);
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f;
|
||||
static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4;
|
||||
|
||||
const uint32_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
|
||||
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
|
||||
|
||||
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
|
||||
bool EntityMotionState::entityTreeIsLocked() const {
|
||||
|
@ -58,8 +60,7 @@ bool entityTreeIsLocked() {
|
|||
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) :
|
||||
ObjectMotionState(shape),
|
||||
_entity(entity),
|
||||
_sentActive(false),
|
||||
_numNonMovingUpdates(0),
|
||||
_sentInactive(true),
|
||||
_lastStep(0),
|
||||
_serverPosition(0.0f),
|
||||
_serverRotation(),
|
||||
|
@ -67,9 +68,13 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
|||
_serverAngularVelocity(0.0f),
|
||||
_serverGravity(0.0f),
|
||||
_serverAcceleration(0.0f),
|
||||
_serverActionData(QByteArray()),
|
||||
_lastMeasureStep(0),
|
||||
_lastVelocity(glm::vec3(0.0f)),
|
||||
_measuredAcceleration(glm::vec3(0.0f)),
|
||||
_measuredDeltaTime(0.0f),
|
||||
_accelerationNearlyGravityCount(0),
|
||||
_candidateForOwnership(false),
|
||||
_loopsSinceOwnershipBid(0),
|
||||
_nextOwnershipBid(0),
|
||||
_loopsWithoutOwner(0)
|
||||
{
|
||||
_type = MOTIONSTATE_TYPE_ENTITY;
|
||||
|
@ -90,31 +95,39 @@ void EntityMotionState::updateServerPhysicsVariables() {
|
|||
_serverVelocity = _entity->getVelocity();
|
||||
_serverAngularVelocity = _entity->getAngularVelocity();
|
||||
_serverAcceleration = _entity->getAcceleration();
|
||||
_serverActionData = _entity->getActionData();
|
||||
}
|
||||
|
||||
// virtual
|
||||
void EntityMotionState::handleEasyChanges(uint32_t flags) {
|
||||
void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
||||
assert(entityTreeIsLocked());
|
||||
updateServerPhysicsVariables();
|
||||
ObjectMotionState::handleEasyChanges(flags);
|
||||
ObjectMotionState::handleEasyChanges(flags, engine);
|
||||
|
||||
if (flags & EntityItem::DIRTY_SIMULATOR_ID) {
|
||||
_loopsWithoutOwner = 0;
|
||||
_candidateForOwnership = 0;
|
||||
if (_entity->getSimulatorID().isNull()
|
||||
&& !_entity->isMoving()
|
||||
&& _body->isActive()) {
|
||||
if (_entity->getSimulatorID().isNull()) {
|
||||
// simulation ownership is being removed
|
||||
// remove the ACTIVATION flag because this object is coming to rest
|
||||
// according to a remote simulation and we don't want to wake it up again
|
||||
flags &= ~EntityItem::DIRTY_PHYSICS_ACTIVATION;
|
||||
// hint to Bullet that the object is deactivating
|
||||
_body->setActivationState(WANTS_DEACTIVATION);
|
||||
} else {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid& sessionID = nodeList->getSessionUUID();
|
||||
if (_entity->getSimulatorID() != sessionID) {
|
||||
_loopsSinceOwnershipBid = 0;
|
||||
_outgoingPriority = NO_PRORITY;
|
||||
} else {
|
||||
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
if (engine->getSessionID() == _entity->getSimulatorID() || _entity->getSimulationPriority() > _outgoingPriority) {
|
||||
// we own the simulation or our priority looses to remote
|
||||
_outgoingPriority = NO_PRORITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flags & EntityItem::DIRTY_SIMULATOR_OWNERSHIP) {
|
||||
// (DIRTY_SIMULATOR_OWNERSHIP really means "we should bid for ownership with SCRIPT priority")
|
||||
// we're manipulating this object directly via script, so we artificially
|
||||
// manipulate the logic to trigger an immediate bid for ownership
|
||||
setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY);
|
||||
}
|
||||
if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
|
||||
_body->activate();
|
||||
}
|
||||
|
@ -191,13 +204,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
|||
if (_entity->getSimulatorID().isNull()) {
|
||||
_loopsWithoutOwner++;
|
||||
|
||||
const uint32_t OWNERSHIP_BID_DELAY = 50;
|
||||
if (_loopsWithoutOwner > OWNERSHIP_BID_DELAY) {
|
||||
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) {
|
||||
//qDebug() << "Warning -- claiming something I saw moving." << getName();
|
||||
_candidateForOwnership = true;
|
||||
setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
}
|
||||
} else {
|
||||
_loopsWithoutOwner = 0;
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
|
@ -228,7 +238,7 @@ bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const {
|
|||
return false;
|
||||
}
|
||||
assert(entityTreeIsLocked());
|
||||
return _candidateForOwnership || sessionID == _entity->getSimulatorID();
|
||||
return _outgoingPriority != NO_PRORITY || sessionID == _entity->getSimulatorID();
|
||||
}
|
||||
|
||||
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
||||
|
@ -241,7 +251,8 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
_serverVelocity = bulletToGLM(_body->getLinearVelocity());
|
||||
_serverAngularVelocity = bulletToGLM(_body->getAngularVelocity());
|
||||
_lastStep = simulationStep;
|
||||
_sentActive = false;
|
||||
_serverActionData = _entity->getActionData();
|
||||
_sentInactive = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -255,7 +266,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
|
||||
const float INACTIVE_UPDATE_PERIOD = 0.5f;
|
||||
if (!_sentActive) {
|
||||
if (_sentInactive) {
|
||||
// we resend the inactive update every INACTIVE_UPDATE_PERIOD
|
||||
// until it is removed from the outgoing updates
|
||||
// (which happens when we don't own the simulation and it isn't touching our simulation)
|
||||
|
@ -275,6 +286,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
_serverPosition += dt * _serverVelocity;
|
||||
}
|
||||
|
||||
if (_serverActionData != _entity->getActionData()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Else we measure the error between current and extrapolated transform (according to expected behavior
|
||||
// of remote EntitySimulation) and return true if the error is significant.
|
||||
|
||||
|
@ -311,7 +326,8 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
// Bullet caps the effective rotation velocity inside its rotation integration step, therefore
|
||||
// we must integrate with the same algorithm and timestep in order achieve similar results.
|
||||
for (int i = 0; i < numSteps; ++i) {
|
||||
_serverRotation = glm::normalize(computeBulletRotationStep(_serverAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _serverRotation);
|
||||
_serverRotation = glm::normalize(computeBulletRotationStep(_serverAngularVelocity,
|
||||
PHYSICS_ENGINE_FIXED_SUBSTEP) * _serverRotation);
|
||||
}
|
||||
}
|
||||
const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation
|
||||
|
@ -343,30 +359,20 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s
|
|||
assert(_body);
|
||||
assert(entityTreeIsLocked());
|
||||
|
||||
if (!remoteSimulationOutOfSync(simulationStep)) {
|
||||
_candidateForOwnership = false;
|
||||
if (_entity->getSimulatorID() != sessionID) {
|
||||
// we don't own the simulation, but maybe we should...
|
||||
if (_outgoingPriority != NO_PRORITY) {
|
||||
if (_outgoingPriority < _entity->getSimulationPriority()) {
|
||||
// our priority looses to remote, so we don't bother to bid
|
||||
_outgoingPriority = NO_PRORITY;
|
||||
return false;
|
||||
}
|
||||
return usecTimestampNow() > _nextOwnershipBid;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_entity->getSimulatorID() == sessionID) {
|
||||
// we own the simulation
|
||||
_candidateForOwnership = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t FRAMES_BETWEEN_OWNERSHIP_CLAIMS = 30;
|
||||
if (_candidateForOwnership) {
|
||||
_candidateForOwnership = false;
|
||||
++_loopsSinceOwnershipBid;
|
||||
if (_loopsSinceOwnershipBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) {
|
||||
// we don't own the simulation, but it's time to bid for it
|
||||
_loopsSinceOwnershipBid = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_candidateForOwnership = false;
|
||||
return false;
|
||||
return remoteSimulationOutOfSync(simulationStep);
|
||||
}
|
||||
|
||||
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) {
|
||||
|
@ -380,7 +386,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
|||
_entity->setVelocity(zero);
|
||||
_entity->setAngularVelocity(zero);
|
||||
_entity->setAcceleration(zero);
|
||||
_sentActive = false;
|
||||
_sentInactive = true;
|
||||
} else {
|
||||
float gravityLength = glm::length(_entity->getGravity());
|
||||
float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength);
|
||||
|
@ -406,9 +412,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
|||
|
||||
const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
|
||||
const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
|
||||
bool movingSlowly = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD)
|
||||
&& glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD)
|
||||
&& _entity->getAcceleration() == glm::vec3(0.0f);
|
||||
|
||||
bool movingSlowlyLinear =
|
||||
glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD);
|
||||
bool movingSlowlyAngular = glm::length2(_entity->getAngularVelocity()) <
|
||||
(DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD);
|
||||
bool movingSlowly = movingSlowlyLinear && movingSlowlyAngular && _entity->getAcceleration() == glm::vec3(0.0f);
|
||||
|
||||
if (movingSlowly) {
|
||||
// velocities might not be zero, but we'll fake them as such, which will hopefully help convince
|
||||
|
@ -417,7 +426,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
|||
_entity->setVelocity(zero);
|
||||
_entity->setAngularVelocity(zero);
|
||||
}
|
||||
_sentActive = true;
|
||||
_sentInactive = false;
|
||||
}
|
||||
|
||||
// remember properties for local server prediction
|
||||
|
@ -426,8 +435,9 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
|||
_serverVelocity = _entity->getVelocity();
|
||||
_serverAcceleration = _entity->getAcceleration();
|
||||
_serverAngularVelocity = _entity->getAngularVelocity();
|
||||
_serverActionData = _entity->getActionData();
|
||||
|
||||
EntityItemProperties properties = _entity->getProperties();
|
||||
EntityItemProperties properties;
|
||||
|
||||
// explicitly set the properties that changed so that they will be packed
|
||||
properties.setPosition(_serverPosition);
|
||||
|
@ -435,6 +445,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
|||
properties.setVelocity(_serverVelocity);
|
||||
properties.setAcceleration(_serverAcceleration);
|
||||
properties.setAngularVelocity(_serverAngularVelocity);
|
||||
properties.setActionData(_serverActionData);
|
||||
|
||||
// set the LastEdited of the properties but NOT the entity itself
|
||||
quint64 now = usecTimestampNow();
|
||||
|
@ -453,14 +464,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
|||
if (!active) {
|
||||
// we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID
|
||||
// but we remember that we do still own it... and rely on the server to tell us that we don't
|
||||
properties.setSimulatorID(QUuid());
|
||||
} else {
|
||||
// explicitly set the property's simulatorID so that it is flagged as changed and will be packed
|
||||
properties.setSimulatorID(sessionID);
|
||||
properties.clearSimulationOwner();
|
||||
_outgoingPriority = NO_PRORITY;
|
||||
}
|
||||
// else the ownership is not changing so we don't bother to pack it
|
||||
} else {
|
||||
// we don't own the simulation for this entity yet, but we're sending a bid for it
|
||||
properties.setSimulatorID(sessionID);
|
||||
properties.setSimulationOwner(sessionID, glm::max<quint8>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY));
|
||||
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
}
|
||||
|
||||
if (EntityItem::getSendPhysicsUpdates()) {
|
||||
|
@ -498,6 +509,13 @@ uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() {
|
|||
return dirtyFlags;
|
||||
}
|
||||
|
||||
// virtual
|
||||
quint8 EntityMotionState::getSimulationPriority() const {
|
||||
if (_entity) {
|
||||
return _entity->getSimulationPriority();
|
||||
}
|
||||
return NO_PRORITY;
|
||||
}
|
||||
|
||||
// virtual
|
||||
QUuid EntityMotionState::getSimulatorID() const {
|
||||
|
@ -508,10 +526,11 @@ QUuid EntityMotionState::getSimulatorID() const {
|
|||
return QUuid();
|
||||
}
|
||||
|
||||
|
||||
// virtual
|
||||
void EntityMotionState::bump() {
|
||||
_candidateForOwnership = true;
|
||||
void EntityMotionState::bump(quint8 priority) {
|
||||
if (_entity) {
|
||||
setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
|
||||
}
|
||||
}
|
||||
|
||||
void EntityMotionState::resetMeasuredBodyAcceleration() {
|
||||
|
@ -539,9 +558,10 @@ void EntityMotionState::measureBodyAcceleration() {
|
|||
glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity());
|
||||
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
|
||||
_lastVelocity = velocity;
|
||||
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS && !_candidateForOwnership) {
|
||||
_loopsSinceOwnershipBid = 0;
|
||||
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
|
||||
_loopsWithoutOwner = 0;
|
||||
_lastStep = ObjectMotionState::getWorldSimulationStep();
|
||||
_sentInactive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -580,3 +600,7 @@ int16_t EntityMotionState::computeCollisionGroup() {
|
|||
}
|
||||
return COLLISION_GROUP_DEFAULT;
|
||||
}
|
||||
|
||||
void EntityMotionState::setOutgoingPriority(quint8 priority) {
|
||||
_outgoingPriority = glm::max<quint8>(_outgoingPriority, priority);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
virtual ~EntityMotionState();
|
||||
|
||||
void updateServerPhysicsVariables();
|
||||
virtual void handleEasyChanges(uint32_t flags);
|
||||
virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||
virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||
|
||||
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
|
||||
|
@ -68,8 +68,9 @@ public:
|
|||
|
||||
virtual const QUuid& getObjectID() const { return _entity->getID(); }
|
||||
|
||||
virtual quint8 getSimulationPriority() const;
|
||||
virtual QUuid getSimulatorID() const;
|
||||
virtual void bump();
|
||||
virtual void bump(quint8 priority);
|
||||
|
||||
EntityItemPointer getEntity() const { return _entity; }
|
||||
|
||||
|
@ -80,6 +81,9 @@ public:
|
|||
|
||||
virtual int16_t computeCollisionGroup();
|
||||
|
||||
// eternal logic can suggest a simuator priority bid for the next outgoing update
|
||||
void setOutgoingPriority(quint8 priority);
|
||||
|
||||
friend class PhysicalEntitySimulation;
|
||||
|
||||
protected:
|
||||
|
@ -93,8 +97,7 @@ protected:
|
|||
|
||||
EntityItemPointer _entity;
|
||||
|
||||
bool _sentActive; // true if body was active when we sent last update
|
||||
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
|
||||
bool _sentInactive; // true if body was inactive when we sent last update
|
||||
|
||||
// these are for the prediction of the remote server's simple extrapolation
|
||||
uint32_t _lastStep; // last step of server extrapolation
|
||||
|
@ -104,6 +107,7 @@ protected:
|
|||
glm::vec3 _serverAngularVelocity; // radians per second
|
||||
glm::vec3 _serverGravity;
|
||||
glm::vec3 _serverAcceleration;
|
||||
QByteArray _serverActionData;
|
||||
|
||||
uint32_t _lastMeasureStep;
|
||||
glm::vec3 _lastVelocity;
|
||||
|
@ -111,9 +115,9 @@ protected:
|
|||
float _measuredDeltaTime;
|
||||
|
||||
quint8 _accelerationNearlyGravityCount;
|
||||
bool _candidateForOwnership;
|
||||
uint32_t _loopsSinceOwnershipBid;
|
||||
quint64 _nextOwnershipBid = NO_PRORITY;
|
||||
uint32_t _loopsWithoutOwner;
|
||||
quint8 _outgoingPriority = NO_PRORITY;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityMotionState_h
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include "ObjectAction.h"
|
||||
|
||||
ObjectAction::ObjectAction(QUuid id, EntityItemPointer ownerEntity) :
|
||||
ObjectAction::ObjectAction(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) :
|
||||
btActionInterface(),
|
||||
_id(id),
|
||||
_active(false),
|
||||
|
@ -27,10 +27,13 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta
|
|||
if (!_active) {
|
||||
return;
|
||||
}
|
||||
if (!_ownerEntity) {
|
||||
qDebug() << "ObjectAction::updateAction no owner entity";
|
||||
if (_ownerEntity.expired()) {
|
||||
qDebug() << "warning -- action with no entity removing self from btCollisionWorld.";
|
||||
btDynamicsWorld* dynamicsWorld = static_cast<btDynamicsWorld*>(collisionWorld);
|
||||
dynamicsWorld->removeAction(this);
|
||||
return;
|
||||
}
|
||||
|
||||
updateActionWorker(deltaTimeStep);
|
||||
}
|
||||
|
||||
|
@ -42,10 +45,11 @@ void ObjectAction::removeFromSimulation(EntitySimulation* simulation) const {
|
|||
}
|
||||
|
||||
btRigidBody* ObjectAction::getRigidBody() {
|
||||
if (!_ownerEntity) {
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (!ownerEntity) {
|
||||
return nullptr;
|
||||
}
|
||||
void* physicsInfo = _ownerEntity->getPhysicsInfo();
|
||||
void* physicsInfo = ownerEntity->getPhysicsInfo();
|
||||
if (!physicsInfo) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -124,3 +128,12 @@ void ObjectAction::setAngularVelocity(glm::vec3 angularVelocity) {
|
|||
rigidBody->setAngularVelocity(glmToBullet(angularVelocity));
|
||||
rigidBody->activate();
|
||||
}
|
||||
|
||||
QByteArray ObjectAction::serialize() {
|
||||
assert(false);
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
void ObjectAction::deserialize(QByteArray serializedArguments) {
|
||||
assert(false);
|
||||
}
|
||||
|
|
|
@ -26,14 +26,17 @@
|
|||
|
||||
class ObjectAction : public btActionInterface, public EntityActionInterface {
|
||||
public:
|
||||
ObjectAction(QUuid id, EntityItemPointer ownerEntity);
|
||||
ObjectAction(EntityActionType type, QUuid id, EntityItemPointer ownerEntity);
|
||||
virtual ~ObjectAction();
|
||||
|
||||
const QUuid& getID() const { return _id; }
|
||||
virtual EntityActionType getType() { assert(false); return ACTION_TYPE_NONE; }
|
||||
virtual void removeFromSimulation(EntitySimulation* simulation) const;
|
||||
virtual const EntityItemPointer& getOwnerEntity() const { return _ownerEntity; }
|
||||
virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; }
|
||||
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; }
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) { return false; }
|
||||
virtual QVariantMap getArguments() { return QVariantMap(); }
|
||||
|
||||
// this is called from updateAction and should be overridden by subclasses
|
||||
virtual void updateActionWorker(float deltaTimeStep) {}
|
||||
|
@ -42,11 +45,15 @@ public:
|
|||
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep);
|
||||
virtual void debugDraw(btIDebugDraw* debugDrawer);
|
||||
|
||||
virtual QByteArray serialize();
|
||||
virtual void deserialize(QByteArray serializedArguments);
|
||||
|
||||
private:
|
||||
QUuid _id;
|
||||
QReadWriteLock _lock;
|
||||
|
||||
protected:
|
||||
|
||||
virtual btRigidBody* getRigidBody();
|
||||
virtual glm::vec3 getPosition();
|
||||
virtual void setPosition(glm::vec3 position);
|
||||
|
@ -57,13 +64,14 @@ protected:
|
|||
virtual glm::vec3 getAngularVelocity();
|
||||
virtual void setAngularVelocity(glm::vec3 angularVelocity);
|
||||
|
||||
void lockForRead() { _lock.lockForRead(); }
|
||||
bool tryLockForRead() { return _lock.tryLockForRead(); }
|
||||
void lockForWrite() { _lock.lockForWrite(); }
|
||||
bool tryLockForWrite() { return _lock.tryLockForWrite(); }
|
||||
void unlock() { _lock.unlock(); }
|
||||
|
||||
bool _active;
|
||||
EntityItemPointer _ownerEntity;
|
||||
EntityItemWeakPointer _ownerEntity;
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectAction_h
|
||||
|
|
|
@ -9,10 +9,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include "ObjectActionOffset.h"
|
||||
|
||||
ObjectActionOffset::ObjectActionOffset(QUuid id, EntityItemPointer ownerEntity) :
|
||||
ObjectAction(id, ownerEntity) {
|
||||
const uint16_t ObjectActionOffset::offsetVersion = 1;
|
||||
|
||||
ObjectActionOffset::ObjectActionOffset(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) :
|
||||
ObjectAction(type, id, ownerEntity) {
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "ObjectActionOffset::ObjectActionOffset";
|
||||
#endif
|
||||
|
@ -30,7 +34,12 @@ void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) {
|
|||
return;
|
||||
}
|
||||
|
||||
void* physicsInfo = _ownerEntity->getPhysicsInfo();
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (!ownerEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
void* physicsInfo = ownerEntity->getPhysicsInfo();
|
||||
if (!physicsInfo) {
|
||||
unlock();
|
||||
return;
|
||||
|
@ -107,3 +116,52 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
|
|||
unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap ObjectActionOffset::getArguments() {
|
||||
QVariantMap arguments;
|
||||
lockForRead();
|
||||
arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom);
|
||||
arguments["linearTimeScale"] = _linearTimeScale;
|
||||
arguments["linearDistance"] = _linearDistance;
|
||||
unlock();
|
||||
return arguments;
|
||||
}
|
||||
|
||||
QByteArray ObjectActionOffset::serialize() {
|
||||
QByteArray ba;
|
||||
QDataStream dataStream(&ba, QIODevice::WriteOnly);
|
||||
dataStream << getType();
|
||||
dataStream << getID();
|
||||
dataStream << ObjectActionOffset::offsetVersion;
|
||||
|
||||
dataStream << _pointToOffsetFrom;
|
||||
dataStream << _linearDistance;
|
||||
dataStream << _linearTimeScale;
|
||||
dataStream << _positionalTargetSet;
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
void ObjectActionOffset::deserialize(QByteArray serializedArguments) {
|
||||
QDataStream dataStream(serializedArguments);
|
||||
|
||||
EntityActionType type;
|
||||
QUuid id;
|
||||
uint16_t serializationVersion;
|
||||
|
||||
dataStream >> type;
|
||||
assert(type == getType());
|
||||
dataStream >> id;
|
||||
assert(id == getID());
|
||||
dataStream >> serializationVersion;
|
||||
if (serializationVersion != ObjectActionOffset::offsetVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataStream >> _pointToOffsetFrom;
|
||||
dataStream >> _linearDistance;
|
||||
dataStream >> _linearTimeScale;
|
||||
dataStream >> _positionalTargetSet;
|
||||
|
||||
_active = true;
|
||||
}
|
||||
|
|
|
@ -19,13 +19,21 @@
|
|||
|
||||
class ObjectActionOffset : public ObjectAction {
|
||||
public:
|
||||
ObjectActionOffset(QUuid id, EntityItemPointer ownerEntity);
|
||||
ObjectActionOffset(EntityActionType type, QUuid id, EntityItemPointer ownerEntity);
|
||||
virtual ~ObjectActionOffset();
|
||||
|
||||
virtual EntityActionType getType() { return ACTION_TYPE_OFFSET; }
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments);
|
||||
virtual QVariantMap getArguments();
|
||||
|
||||
virtual void updateActionWorker(float deltaTimeStep);
|
||||
|
||||
private:
|
||||
virtual QByteArray serialize();
|
||||
virtual void deserialize(QByteArray serializedArguments);
|
||||
|
||||
private:
|
||||
static const uint16_t offsetVersion;
|
||||
glm::vec3 _pointToOffsetFrom;
|
||||
float _linearDistance;
|
||||
float _linearTimeScale;
|
||||
|
|
|
@ -9,10 +9,22 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include "ObjectActionSpring.h"
|
||||
|
||||
ObjectActionSpring::ObjectActionSpring(QUuid id, EntityItemPointer ownerEntity) :
|
||||
ObjectAction(id, ownerEntity) {
|
||||
const float SPRING_MAX_SPEED = 10.0f;
|
||||
|
||||
const uint16_t ObjectActionSpring::springVersion = 1;
|
||||
|
||||
ObjectActionSpring::ObjectActionSpring(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) :
|
||||
ObjectAction(type, id, ownerEntity),
|
||||
_positionalTarget(glm::vec3(0.0f)),
|
||||
_linearTimeScale(0.2f),
|
||||
_positionalTargetSet(false),
|
||||
_rotationalTarget(glm::quat()),
|
||||
_angularTimeScale(0.2f),
|
||||
_rotationalTargetSet(false) {
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "ObjectActionSpring::ObjectActionSpring";
|
||||
#endif
|
||||
|
@ -31,7 +43,12 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
|
|||
return;
|
||||
}
|
||||
|
||||
void* physicsInfo = _ownerEntity->getPhysicsInfo();
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (!ownerEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
void* physicsInfo = ownerEntity->getPhysicsInfo();
|
||||
if (!physicsInfo) {
|
||||
unlock();
|
||||
return;
|
||||
|
@ -46,10 +63,26 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
|
|||
|
||||
// handle the linear part
|
||||
if (_positionalTargetSet) {
|
||||
// check for NaN
|
||||
if (_positionalTarget.x != _positionalTarget.x ||
|
||||
_positionalTarget.y != _positionalTarget.y ||
|
||||
_positionalTarget.z != _positionalTarget.z) {
|
||||
qDebug() << "ObjectActionSpring::updateActionWorker -- target position includes NaN";
|
||||
unlock();
|
||||
lockForWrite();
|
||||
_active = false;
|
||||
unlock();
|
||||
return;
|
||||
}
|
||||
glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition());
|
||||
float offsetLength = glm::length(offset);
|
||||
float speed = offsetLength / _linearTimeScale;
|
||||
|
||||
// cap speed
|
||||
if (speed > SPRING_MAX_SPEED) {
|
||||
speed = SPRING_MAX_SPEED;
|
||||
}
|
||||
|
||||
if (offsetLength > IGNORE_POSITION_DELTA) {
|
||||
glm::vec3 newVelocity = glm::normalize(offset) * speed;
|
||||
rigidBody->setLinearVelocity(glmToBullet(newVelocity));
|
||||
|
@ -61,6 +94,18 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
|
|||
|
||||
// handle rotation
|
||||
if (_rotationalTargetSet) {
|
||||
if (_rotationalTarget.x != _rotationalTarget.x ||
|
||||
_rotationalTarget.y != _rotationalTarget.y ||
|
||||
_rotationalTarget.z != _rotationalTarget.z ||
|
||||
_rotationalTarget.w != _rotationalTarget.w) {
|
||||
qDebug() << "AvatarActionHold::updateActionWorker -- target rotation includes NaN";
|
||||
unlock();
|
||||
lockForWrite();
|
||||
_active = false;
|
||||
unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
glm::quat bodyRotation = bulletToGLM(rigidBody->getOrientation());
|
||||
// if qZero and qOne are too close to each other, we can get NaN for angle.
|
||||
auto alignmentDot = glm::dot(bodyRotation, _rotationalTarget);
|
||||
|
@ -108,7 +153,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
|||
EntityActionInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", rscOk, false);
|
||||
|
||||
if (!ptOk && !rtOk) {
|
||||
qDebug() << "spring action requires either targetPosition or targetRotation argument";
|
||||
qDebug() << "spring action requires at least one of targetPosition or targetRotation argument";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -142,3 +187,67 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
|||
unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap ObjectActionSpring::getArguments() {
|
||||
QVariantMap arguments;
|
||||
lockForRead();
|
||||
|
||||
if (_positionalTargetSet) {
|
||||
arguments["linearTimeScale"] = _linearTimeScale;
|
||||
arguments["targetPosition"] = glmToQMap(_positionalTarget);
|
||||
}
|
||||
|
||||
if (_rotationalTargetSet) {
|
||||
arguments["targetRotation"] = glmToQMap(_rotationalTarget);
|
||||
arguments["angularTimeScale"] = _angularTimeScale;
|
||||
}
|
||||
|
||||
unlock();
|
||||
return arguments;
|
||||
}
|
||||
|
||||
QByteArray ObjectActionSpring::serialize() {
|
||||
QByteArray serializedActionArguments;
|
||||
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
|
||||
|
||||
dataStream << getType();
|
||||
dataStream << getID();
|
||||
dataStream << ObjectActionSpring::springVersion;
|
||||
|
||||
dataStream << _positionalTarget;
|
||||
dataStream << _linearTimeScale;
|
||||
dataStream << _positionalTargetSet;
|
||||
|
||||
dataStream << _rotationalTarget;
|
||||
dataStream << _angularTimeScale;
|
||||
dataStream << _rotationalTargetSet;
|
||||
|
||||
return serializedActionArguments;
|
||||
}
|
||||
|
||||
void ObjectActionSpring::deserialize(QByteArray serializedArguments) {
|
||||
QDataStream dataStream(serializedArguments);
|
||||
|
||||
EntityActionType type;
|
||||
QUuid id;
|
||||
uint16_t serializationVersion;
|
||||
|
||||
dataStream >> type;
|
||||
assert(type == getType());
|
||||
dataStream >> id;
|
||||
assert(id == getID());
|
||||
dataStream >> serializationVersion;
|
||||
if (serializationVersion != ObjectActionSpring::springVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataStream >> _positionalTarget;
|
||||
dataStream >> _linearTimeScale;
|
||||
dataStream >> _positionalTargetSet;
|
||||
|
||||
dataStream >> _rotationalTarget;
|
||||
dataStream >> _angularTimeScale;
|
||||
dataStream >> _rotationalTargetSet;
|
||||
|
||||
_active = true;
|
||||
}
|
||||
|
|
|
@ -19,13 +19,21 @@
|
|||
|
||||
class ObjectActionSpring : public ObjectAction {
|
||||
public:
|
||||
ObjectActionSpring(QUuid id, EntityItemPointer ownerEntity);
|
||||
ObjectActionSpring(EntityActionType type, QUuid id, EntityItemPointer ownerEntity);
|
||||
virtual ~ObjectActionSpring();
|
||||
|
||||
virtual EntityActionType getType() { return ACTION_TYPE_SPRING; }
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments);
|
||||
virtual QVariantMap getArguments();
|
||||
|
||||
virtual void updateActionWorker(float deltaTimeStep);
|
||||
|
||||
virtual QByteArray serialize();
|
||||
virtual void deserialize(QByteArray serializedArguments);
|
||||
|
||||
protected:
|
||||
static const uint16_t springVersion;
|
||||
|
||||
glm::vec3 _positionalTarget;
|
||||
float _linearTimeScale;
|
||||
|
|
|
@ -114,7 +114,7 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
|||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::handleEasyChanges(uint32_t flags) {
|
||||
void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) {
|
||||
if (flags & EntityItem::DIRTY_POSITION) {
|
||||
btTransform worldTrans;
|
||||
if (flags & EntityItem::DIRTY_ROTATION) {
|
||||
|
@ -159,7 +159,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine*
|
|||
if ((flags & HARD_DIRTY_PHYSICS_FLAGS) == 0) {
|
||||
// no HARD flags remain, so do any EASY changes
|
||||
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
handleEasyChanges(flags);
|
||||
handleEasyChanges(flags, engine);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine*
|
|||
}
|
||||
}
|
||||
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
handleEasyChanges(flags);
|
||||
handleEasyChanges(flags, engine);
|
||||
}
|
||||
// it is possible that there are no HARD flags at this point (if DIRTY_SHAPE was removed)
|
||||
// so we check again befoe we reinsert:
|
||||
|
|
|
@ -40,7 +40,8 @@ enum MotionStateType {
|
|||
const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE);
|
||||
const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES |
|
||||
EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP |
|
||||
EntityItem::DIRTY_MATERIAL);
|
||||
EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_SIMULATOR_ID |
|
||||
EntityItem::DIRTY_SIMULATOR_OWNERSHIP);
|
||||
|
||||
// These are the set of incoming flags that the PhysicsEngine needs to hear about:
|
||||
const uint32_t DIRTY_PHYSICS_FLAGS = (uint32_t)(HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS |
|
||||
|
@ -70,7 +71,7 @@ public:
|
|||
ObjectMotionState(btCollisionShape* shape);
|
||||
~ObjectMotionState();
|
||||
|
||||
virtual void handleEasyChanges(uint32_t flags);
|
||||
virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||
virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine);
|
||||
|
||||
void updateBodyMaterialProperties();
|
||||
|
@ -118,8 +119,9 @@ public:
|
|||
|
||||
virtual const QUuid& getObjectID() const = 0;
|
||||
|
||||
virtual quint8 getSimulationPriority() const { return 0; }
|
||||
virtual QUuid getSimulatorID() const = 0;
|
||||
virtual void bump() = 0;
|
||||
virtual void bump(quint8 priority) {}
|
||||
|
||||
virtual QString getName() { return ""; }
|
||||
|
||||
|
|
|
@ -235,14 +235,32 @@ void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionE
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void PhysicalEntitySimulation::addAction(EntityActionPointer action) {
|
||||
if (_physicsEngine) {
|
||||
lock();
|
||||
const QUuid& actionID = action->getID();
|
||||
if (_physicsEngine->getActionByID(actionID)) {
|
||||
qDebug() << "warning -- PhysicalEntitySimulation::addAction -- adding an action that was already in _physicsEngine";
|
||||
}
|
||||
unlock();
|
||||
EntitySimulation::addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::applyActionChanges() {
|
||||
if (_physicsEngine) {
|
||||
foreach (EntityActionPointer actionToAdd, _actionsToAdd) {
|
||||
_physicsEngine->addAction(actionToAdd);
|
||||
}
|
||||
lock();
|
||||
foreach (QUuid actionToRemove, _actionsToRemove) {
|
||||
_physicsEngine->removeAction(actionToRemove);
|
||||
}
|
||||
_actionsToRemove.clear();
|
||||
foreach (EntityActionPointer actionToAdd, _actionsToAdd) {
|
||||
if (!_actionsToRemove.contains(actionToAdd->getID())) {
|
||||
_physicsEngine->addAction(actionToAdd);
|
||||
}
|
||||
}
|
||||
_actionsToAdd.clear();
|
||||
unlock();
|
||||
}
|
||||
EntitySimulation::applyActionChanges();
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
|
||||
void init(EntityTree* tree, PhysicsEngine* engine, EntityEditPacketSender* packetSender);
|
||||
|
||||
virtual void addAction(EntityActionPointer action);
|
||||
virtual void applyActionChanges();
|
||||
|
||||
protected: // only called by EntitySimulation
|
||||
|
|
|
@ -142,7 +142,7 @@ void PhysicsEngine::addObject(ObjectMotionState* motionState) {
|
|||
|
||||
motionState->getAndClearIncomingDirtyFlags();
|
||||
}
|
||||
|
||||
|
||||
void PhysicsEngine::removeObject(ObjectMotionState* object) {
|
||||
// wake up anything touching this object
|
||||
bump(object);
|
||||
|
@ -172,7 +172,7 @@ void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) {
|
|||
for (auto object : objects) {
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
removeObject(object);
|
||||
|
||||
|
||||
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||
object->setRigidBody(nullptr);
|
||||
body->setMotionState(nullptr);
|
||||
|
@ -194,7 +194,7 @@ void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) {
|
|||
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
||||
object->handleHardAndEasyChanges(flags, this);
|
||||
} else if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
object->handleEasyChanges(flags);
|
||||
object->handleEasyChanges(flags, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,26 +260,29 @@ void PhysicsEngine::stepSimulation() {
|
|||
|
||||
void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) {
|
||||
BT_PROFILE("ownershipInfection");
|
||||
if (_sessionID.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const btCollisionObject* characterObject = _characterController ? _characterController->getCollisionObject() : nullptr;
|
||||
|
||||
ObjectMotionState* a = static_cast<ObjectMotionState*>(objectA->getUserPointer());
|
||||
ObjectMotionState* b = static_cast<ObjectMotionState*>(objectB->getUserPointer());
|
||||
ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(objectA->getUserPointer());
|
||||
ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(objectB->getUserPointer());
|
||||
|
||||
if (b && ((a && a->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || (objectA == characterObject))) {
|
||||
// NOTE: we might own the simulation of a kinematic object (A)
|
||||
if (motionStateB &&
|
||||
((motionStateA && motionStateA->getSimulatorID() == _sessionID && !objectA->isStaticObject()) ||
|
||||
(objectA == characterObject))) {
|
||||
// NOTE: we might own the simulation of a kinematic object (A)
|
||||
// but we don't claim ownership of kinematic objects (B) based on collisions here.
|
||||
if (!objectB->isStaticOrKinematicObject()) {
|
||||
b->bump();
|
||||
if (!objectB->isStaticOrKinematicObject() && motionStateB->getSimulatorID() != _sessionID) {
|
||||
quint8 priority = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
|
||||
motionStateB->bump(priority);
|
||||
}
|
||||
} else if (a && ((b && b->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || (objectB == characterObject))) {
|
||||
// SIMILARLY: we might own the simulation of a kinematic object (B)
|
||||
} else if (motionStateA &&
|
||||
((motionStateB && motionStateB->getSimulatorID() == _sessionID && !objectB->isStaticObject()) ||
|
||||
(objectB == characterObject))) {
|
||||
// SIMILARLY: we might own the simulation of a kinematic object (B)
|
||||
// but we don't claim ownership of kinematic objects (A) based on collisions here.
|
||||
if (!objectA->isStaticOrKinematicObject()) {
|
||||
a->bump();
|
||||
if (!objectA->isStaticOrKinematicObject() && motionStateA->getSimulatorID() != _sessionID) {
|
||||
quint8 priority = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
|
||||
motionStateA->bump(priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -293,13 +296,13 @@ void PhysicsEngine::updateContactMap() {
|
|||
for (int i = 0; i < numManifolds; ++i) {
|
||||
btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i);
|
||||
if (contactManifold->getNumContacts() > 0) {
|
||||
// TODO: require scripts to register interest in callbacks for specific objects
|
||||
// TODO: require scripts to register interest in callbacks for specific objects
|
||||
// so we can filter out most collision events right here.
|
||||
const btCollisionObject* objectA = static_cast<const btCollisionObject*>(contactManifold->getBody0());
|
||||
const btCollisionObject* objectB = static_cast<const btCollisionObject*>(contactManifold->getBody1());
|
||||
|
||||
if (!(objectA->isActive() || objectB->isActive())) {
|
||||
// both objects are inactive so stop tracking this contact,
|
||||
// both objects are inactive so stop tracking this contact,
|
||||
// which will eventually trigger a CONTACT_EVENT_TYPE_END
|
||||
continue;
|
||||
}
|
||||
|
@ -311,7 +314,9 @@ void PhysicsEngine::updateContactMap() {
|
|||
_contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0));
|
||||
}
|
||||
|
||||
doOwnershipInfection(objectA, objectB);
|
||||
if (!_sessionID.isNull()) {
|
||||
doOwnershipInfection(objectA, objectB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -327,24 +332,24 @@ CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
|||
ContactInfo& contact = contactItr->second;
|
||||
ContactEventType type = contact.computeType(_numContactFrames);
|
||||
if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) {
|
||||
ObjectMotionState* A = static_cast<ObjectMotionState*>(contactItr->first._a);
|
||||
ObjectMotionState* B = static_cast<ObjectMotionState*>(contactItr->first._b);
|
||||
glm::vec3 velocityChange = (A ? A->getObjectLinearVelocityChange() : glm::vec3(0.0f)) +
|
||||
(B ? B->getObjectLinearVelocityChange() : glm::vec3(0.0f));
|
||||
ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(contactItr->first._a);
|
||||
ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(contactItr->first._b);
|
||||
glm::vec3 velocityChange = (motionStateA ? motionStateA->getObjectLinearVelocityChange() : glm::vec3(0.0f)) +
|
||||
(motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f));
|
||||
|
||||
if (A && A->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
QUuid idA = A->getObjectID();
|
||||
if (motionStateA && motionStateA->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
QUuid idA = motionStateA->getObjectID();
|
||||
QUuid idB;
|
||||
if (B && B->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
idB = B->getObjectID();
|
||||
if (motionStateB && motionStateB->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
idB = motionStateB->getObjectID();
|
||||
}
|
||||
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset;
|
||||
glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB);
|
||||
_collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange));
|
||||
} else if (B && B->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
QUuid idB = B->getObjectID();
|
||||
} else if (motionStateB && motionStateB->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
QUuid idB = motionStateB->getObjectID();
|
||||
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset;
|
||||
// NOTE: we're flipping the order of A and B (so that the first objectID is never NULL)
|
||||
// NOTE: we're flipping the order of A and B (so that the first objectID is never NULL)
|
||||
// hence we must negate the penetration.
|
||||
glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB);
|
||||
_collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration, velocityChange));
|
||||
|
@ -402,7 +407,7 @@ void PhysicsEngine::bump(ObjectMotionState* motionState) {
|
|||
if (!objectA->isStaticOrKinematicObject()) {
|
||||
ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(objectA->getUserPointer());
|
||||
if (motionStateA) {
|
||||
motionStateA->bump();
|
||||
motionStateA->bump(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
objectA->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
}
|
||||
|
@ -410,7 +415,7 @@ void PhysicsEngine::bump(ObjectMotionState* motionState) {
|
|||
if (!objectB->isStaticOrKinematicObject()) {
|
||||
ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(objectB->getUserPointer());
|
||||
if (motionStateB) {
|
||||
motionStateB->bump();
|
||||
motionStateB->bump(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
objectB->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
}
|
||||
|
@ -436,12 +441,21 @@ int16_t PhysicsEngine::getCollisionMask(int16_t group) const {
|
|||
return mask ? *mask : COLLISION_MASK_DEFAULT;
|
||||
}
|
||||
|
||||
EntityActionPointer PhysicsEngine::getActionByID(const QUuid& actionID) const {
|
||||
if (_objectActions.contains(actionID)) {
|
||||
return _objectActions[actionID];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PhysicsEngine::addAction(EntityActionPointer action) {
|
||||
assert(action);
|
||||
const QUuid& actionID = action->getID();
|
||||
if (_objectActions.contains(actionID)) {
|
||||
assert(_objectActions[actionID] == action);
|
||||
return;
|
||||
if (_objectActions[actionID] == action) {
|
||||
return;
|
||||
}
|
||||
removeAction(action->getID());
|
||||
}
|
||||
|
||||
_objectActions[actionID] = action;
|
||||
|
|
|
@ -94,6 +94,7 @@ public:
|
|||
|
||||
int16_t getCollisionMask(int16_t group) const;
|
||||
|
||||
EntityActionPointer getActionByID(const QUuid& actionID) const;
|
||||
void addAction(EntityActionPointer action);
|
||||
void removeAction(const QUuid actionID);
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
#include "gpu/GLBackend.h"
|
||||
|
||||
#include "simple_vert.h"
|
||||
#include "simple_frag.h"
|
||||
#include "simple_textured_frag.h"
|
||||
#include "simple_textured_emisive_frag.h"
|
||||
|
||||
#include "deferred_light_vert.h"
|
||||
#include "deferred_light_limited_vert.h"
|
||||
|
@ -52,15 +52,15 @@ static const std::string glowIntensityShaderHandle = "glowIntensity";
|
|||
|
||||
void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) {
|
||||
auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(simple_vert)));
|
||||
auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_frag)));
|
||||
auto PSTextured = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_frag)));
|
||||
auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_frag)));
|
||||
auto PSEmissive = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_emisive_frag)));
|
||||
|
||||
gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS));
|
||||
gpu::ShaderPointer programTextured = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PSTextured));
|
||||
gpu::ShaderPointer programEmissive = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PSEmissive));
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
gpu::Shader::makeProgram(*programTextured, slotBindings);
|
||||
gpu::Shader::makeProgram(*programEmissive, slotBindings);
|
||||
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
state->setCullMode(gpu::State::CULL_BACK);
|
||||
|
@ -79,8 +79,8 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) {
|
|||
|
||||
_simpleProgram = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
|
||||
_simpleProgramCullNone = gpu::PipelinePointer(gpu::Pipeline::create(program, stateCullNone));
|
||||
_simpleProgramTextured = gpu::PipelinePointer(gpu::Pipeline::create(programTextured, state));
|
||||
_simpleProgramTexturedCullNone = gpu::PipelinePointer(gpu::Pipeline::create(programTextured, stateCullNone));
|
||||
_simpleProgramEmissive = gpu::PipelinePointer(gpu::Pipeline::create(programEmissive, state));
|
||||
_simpleProgramEmissiveCullNone = gpu::PipelinePointer(gpu::Pipeline::create(programEmissive, stateCullNone));
|
||||
|
||||
_viewState = viewState;
|
||||
loadLightProgram(directional_light_frag, false, _directionalLight, _directionalLightLocations);
|
||||
|
@ -117,14 +117,12 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) {
|
|||
lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(_ambientLightMode % gpu::SphericalHarmonics::NUM_PRESET));
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::bindSimpleProgram(gpu::Batch& batch, bool textured, bool culled) {
|
||||
// DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers(batch, true, true, true);
|
||||
|
||||
if (textured) {
|
||||
void DeferredLightingEffect::bindSimpleProgram(gpu::Batch& batch, bool textured, bool culled, bool emmisive) {
|
||||
if (emmisive) {
|
||||
if (culled) {
|
||||
batch.setPipeline(_simpleProgramTextured);
|
||||
batch.setPipeline(_simpleProgramEmissive);
|
||||
} else {
|
||||
batch.setPipeline(_simpleProgramTexturedCullNone);
|
||||
batch.setPipeline(_simpleProgramEmissiveCullNone);
|
||||
}
|
||||
} else {
|
||||
if (culled) {
|
||||
|
@ -133,48 +131,42 @@ void DeferredLightingEffect::bindSimpleProgram(gpu::Batch& batch, bool textured,
|
|||
batch.setPipeline(_simpleProgramCullNone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::releaseSimpleProgram(gpu::Batch& batch) {
|
||||
// DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers(batch, true, false, false);
|
||||
if (!textured) {
|
||||
// If it is not textured, bind white texture and keep using textured pipeline
|
||||
batch.setUniformTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
|
||||
}
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::renderSolidSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color) {
|
||||
bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, radius, slices, stacks, color);
|
||||
releaseSimpleProgram(batch);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::renderWireSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color) {
|
||||
bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, radius, slices, stacks, color, false);
|
||||
releaseSimpleProgram(batch);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::renderSolidCube(gpu::Batch& batch, float size, const glm::vec4& color) {
|
||||
bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidCube(batch, size, color);
|
||||
releaseSimpleProgram(batch);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::renderWireCube(gpu::Batch& batch, float size, const glm::vec4& color) {
|
||||
bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderWireCube(batch, size, color);
|
||||
releaseSimpleProgram(batch);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, const glm::vec3& maxCorner,
|
||||
const glm::vec4& color) {
|
||||
bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, minCorner, maxCorner, color);
|
||||
releaseSimpleProgram(batch);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2,
|
||||
const glm::vec4& color1, const glm::vec4& color2) {
|
||||
bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderLine(batch, p1, p2, color1, color2);
|
||||
releaseSimpleProgram(batch);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color,
|
||||
|
|
|
@ -34,10 +34,7 @@ public:
|
|||
void init(AbstractViewStateInterface* viewState);
|
||||
|
||||
/// Sets up the state necessary to render static untextured geometry with the simple program.
|
||||
void bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool culled = true);
|
||||
|
||||
/// Tears down the state necessary to render static untextured geometry with the simple program.
|
||||
void releaseSimpleProgram(gpu::Batch& batch);
|
||||
void bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool culled = true, bool emmisive = false);
|
||||
|
||||
//// Renders a solid sphere with the simple program.
|
||||
void renderSolidSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color);
|
||||
|
@ -101,8 +98,8 @@ private:
|
|||
|
||||
gpu::PipelinePointer _simpleProgram;
|
||||
gpu::PipelinePointer _simpleProgramCullNone;
|
||||
gpu::PipelinePointer _simpleProgramTextured;
|
||||
gpu::PipelinePointer _simpleProgramTexturedCullNone;
|
||||
gpu::PipelinePointer _simpleProgramEmissive;
|
||||
gpu::PipelinePointer _simpleProgramEmissiveCullNone;
|
||||
|
||||
ProgramObject _directionalSkyboxLight;
|
||||
LightLocations _directionalSkyboxLightLocations;
|
||||
|
|
|
@ -1058,13 +1058,13 @@ void GeometryCache::renderBevelCornersRect(gpu::Batch& batch, int x, int y, int
|
|||
int vertexPoint = 0;
|
||||
|
||||
// Triangle strip points
|
||||
// 3 ------ 5
|
||||
// / \
|
||||
// 1 7
|
||||
// | |
|
||||
// 2 8
|
||||
// \ /
|
||||
// 4 ------ 6
|
||||
// 3 ------ 5 //
|
||||
// / \ //
|
||||
// 1 7 //
|
||||
// | | //
|
||||
// 2 8 //
|
||||
// \ / //
|
||||
// 4 ------ 6 //
|
||||
|
||||
// 1
|
||||
vertexBuffer[vertexPoint++] = x;
|
||||
|
|
|
@ -44,9 +44,9 @@ void main() {
|
|||
if (a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
|
||||
// final color
|
||||
gl_FragData[0] = vec4(Color.rgb, Color.a * a);
|
||||
gl_FragData[1] = vec4(interpolatedNormal.xyz, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0);
|
||||
gl_FragData[2] = vec4(0.0);
|
||||
gl_FragData[1] = vec4(normalize(interpolatedNormal.xyz), 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5);
|
||||
gl_FragData[2] = vec4(Color.rgb, gl_FrontMaterial.shininess / 128.0);
|
||||
}
|
33
libraries/render-utils/src/simple_textured_emisive.slf
Normal file
33
libraries/render-utils/src/simple_textured_emisive.slf
Normal file
|
@ -0,0 +1,33 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// simple.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Clément Brisset on 5/29/15.
|
||||
// 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 DeferredBufferWrite.slh@>
|
||||
|
||||
// the diffuse texture
|
||||
uniform sampler2D originalTexture;
|
||||
|
||||
// the interpolated normal
|
||||
varying vec4 interpolatedNormal;
|
||||
|
||||
void main(void) {
|
||||
vec4 texel = texture2D(originalTexture, gl_TexCoord[0].st);
|
||||
|
||||
packDeferredFragmentLightmap(
|
||||
normalize(interpolatedNormal.xyz),
|
||||
glowIntensity * texel.a,
|
||||
gl_Color.rgb,
|
||||
gl_FrontMaterial.specular.rgb,
|
||||
gl_FrontMaterial.shininess,
|
||||
texel.rgb);
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 4;
|
||||
const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 6; // Bullet will start to "lose time" at 10 FPS.
|
||||
const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f;
|
||||
|
||||
// return incremental rotation (Bullet-style) caused by angularVelocity over timeStep
|
||||
|
|
|
@ -24,6 +24,22 @@ QVariantList rgbColorToQList(rgbColor& v) {
|
|||
return QVariantList() << (int)(v[0]) << (int)(v[1]) << (int)(v[2]);
|
||||
}
|
||||
|
||||
QVariantMap glmToQMap(const glm::vec3& glmVector) {
|
||||
QVariantMap vectorAsVariantMap;
|
||||
vectorAsVariantMap["x"] = glmVector.x;
|
||||
vectorAsVariantMap["y"] = glmVector.y;
|
||||
vectorAsVariantMap["z"] = glmVector.z;
|
||||
return vectorAsVariantMap;
|
||||
}
|
||||
|
||||
QVariantMap glmToQMap(const glm::quat& glmQuat) {
|
||||
QVariantMap quatAsVariantMap;
|
||||
quatAsVariantMap["x"] = glmQuat.x;
|
||||
quatAsVariantMap["y"] = glmQuat.y;
|
||||
quatAsVariantMap["z"] = glmQuat.z;
|
||||
quatAsVariantMap["w"] = glmQuat.w;
|
||||
return quatAsVariantMap;
|
||||
}
|
||||
|
||||
|
||||
glm::vec3 qListToGlmVec3(const QVariant q) {
|
||||
|
|
|
@ -21,6 +21,9 @@ QVariantList glmToQList(const glm::vec3& g);
|
|||
QVariantList glmToQList(const glm::quat& g);
|
||||
QVariantList rgbColorToQList(rgbColor& v);
|
||||
|
||||
QVariantMap glmToQMap(const glm::vec3& glmVector);
|
||||
QVariantMap glmToQMap(const glm::quat& glmQuat);
|
||||
|
||||
glm::vec3 qListToGlmVec3(const QVariant q);
|
||||
glm::quat qListToGlmQuat(const QVariant q);
|
||||
void qListtoRgbColor(const QVariant q, rgbColor returnValue);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue