Merge remote-tracking branch 'clement/protocol' into atp

This commit is contained in:
Stephen Birarda 2015-07-02 16:19:07 -07:00
commit f057be0982
103 changed files with 2519 additions and 639 deletions

View 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.";
}

View 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

View 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;
}

View 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

View file

@ -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();

View file

@ -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")] =

View 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
View 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);

View file

@ -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) {

View file

@ -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 });
}

View file

@ -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();

View file

@ -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 ()

View file

@ -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) {

View file

@ -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);

View file

@ -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;
}

View file

@ -23,6 +23,9 @@ public:
QUuid id,
EntityItemPointer ownerEntity,
QVariantMap arguments);
virtual EntityActionPointer factoryBA(EntitySimulation* simulation,
EntityItemPointer ownerEntity,
QByteArray data);
};
#endif // hifi_InterfaceActionFactory_h

View file

@ -12,3 +12,4 @@
#include "InterfaceLogging.h"
Q_LOGGING_CATEGORY(interfaceapp, "hifi.interface")
Q_LOGGING_CATEGORY(interfaceapp_timing, "hifi.interface.timing")

View file

@ -15,5 +15,6 @@
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(interfaceapp)
Q_DECLARE_LOGGING_CATEGORY(interfaceapp_timing)
#endif // hifi_InterfaceLogging_h

View file

@ -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>();

View file

@ -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";

View file

@ -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);
}
}

View file

@ -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) { }

View file

@ -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);
}

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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);

View file

@ -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();
}

View file

@ -56,7 +56,7 @@ private:
Avatar* _owningAvatar;
void renderHandTargets(bool isMine);
void renderHandTargets(RenderArgs* renderArgs, bool isMine);
};
#endif // hifi_Hand_h

View file

@ -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);
}

View file

@ -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));
}
}
}

View file

@ -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;

View file

@ -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() {

View file

@ -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; }

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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());

View file

@ -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());
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Avatar collision sound URL &lt;span style=&quot; color:#909090;&quot;&gt;(optional)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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">

View file

@ -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;

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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,

View file

@ -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;
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -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";

View file

@ -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();
}

View file

@ -28,7 +28,7 @@ protected:
virtual void changeEntityInternal(EntityItemPointer entity);
virtual void clearEntitiesInternal();
SetOfEntities _hasSimulationOwnerEntities;
SetOfEntities _entitiesWithSimulator;
};
#endif // hifi_SimpleEntitySimulation_h

View 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;
}

View 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

View file

@ -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);

View file

@ -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) {}

View file

@ -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 = &currentMap[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);

View file

@ -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)

View 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();
}

View 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

View file

@ -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:

View file

@ -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

View file

@ -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
}

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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:

View file

@ -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 ""; }

View file

@ -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();
}

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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;

View file

@ -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;

View file

@ -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);
}

View 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);
}

View file

@ -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

View file

@ -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) {

View file

@ -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