Merge branch 'master' of git://github.com/highfidelity/hifi into 20304

This commit is contained in:
Virendra Singh 2015-03-10 22:36:18 +05:30
commit ce4226d6b9
114 changed files with 2741 additions and 868 deletions

View file

@ -181,4 +181,4 @@ endif ()
if (ANDROID OR DESKTOP_GVR)
add_subdirectory(gvr-interface)
endif ()
endif ()

View file

@ -92,7 +92,6 @@ function checkControllerSide(whichSide) {
Vec3.multiply(1.0 - AVERAGE_FACTOR, averageLinearVelocity[0]));
linearVelocity = averageLinearVelocity[0];
angularVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getSpatialControlRawAngularVelocity(LEFT_TIP));
angularVelocity = Vec3.multiply(180.0 / Math.PI, angularVelocity);
} else {
BUTTON_FWD = RIGHT_BUTTON_FWD;
BUTTON_3 = RIGHT_BUTTON_3;
@ -104,7 +103,6 @@ function checkControllerSide(whichSide) {
Vec3.multiply(1.0 - AVERAGE_FACTOR, averageLinearVelocity[1]));
linearVelocity = averageLinearVelocity[1];
angularVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getSpatialControlRawAngularVelocity(RIGHT_TIP));
angularVelocity = Vec3.multiply(180.0 / Math.PI, angularVelocity);
handMessage = "RIGHT";
}

View file

@ -86,7 +86,7 @@ keyboard.onKeyRelease = function(event) {
var textLines = textText.split("\n");
var maxLineWidth = Overlays.textSize(textSizeMeasureOverlay, textText).width;
var usernameLine = "--" + GlobalServices.myUsername;
var usernameLine = "--" + GlobalServices.username;
var usernameWidth = Overlays.textSize(textSizeMeasureOverlay, usernameLine).width;
if (maxLineWidth < usernameWidth) {
maxLineWidth = usernameWidth;

View file

@ -53,7 +53,9 @@ function shootDice(position, velocity) {
position: position,
velocity: velocity,
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
angularVelocity: { x: Math.random() * 100, y: Math.random() * 100, z: Math.random() * 100 },
// NOTE: angularVelocity is in radians/sec
var maxAngularSpeed = Math.PI;
angularVelocity: { x: Math.random() * maxAngularSpeed, y: Math.random() * maxAngularSpeed, z: Math.random() * maxAngularSpeed },
lifetime: LIFETIME,
gravity: { x: 0, y: GRAVITY, z: 0 },
shapeType: "box",
@ -108,4 +110,4 @@ function scriptEnding() {
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
Controller.mousePressEvent.connect(mousePressEvent);
Script.scriptEnding.connect(scriptEnding);
Script.scriptEnding.connect(scriptEnding);

View file

@ -385,7 +385,7 @@ var toolBar = (function () {
} else if (browseModelsButtonDown) {
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (browseModelsButton === toolBar.clicked(clickedOverlay)) {
url = Window.s3Browse(".*(fbx|FBX)");
url = Window.s3Browse(".*(fbx|FBX|obj|OBJ)");
if (url !== null && url !== "") {
addModel(url);
}

View file

@ -11,11 +11,13 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var DEGREES_TO_RADIANS = Math.PI / 180.0;
var lightProperties = {
type: "Light",
position: { x: 0, y: 0, z: 0 },
dimensions: { x: 1000, y: 1000, z: 1000 },
angularVelocity: { x: 0, y: 10, z: 0 },
angularVelocity: { x: 0, y: 10 * DEGREES_TO_RADIANS, z: 0 },
angularDamping: 0,
isSpotlight: true,

16
examples/hmdDefaults.js Normal file
View file

@ -0,0 +1,16 @@
//
// hmdDefaults.js
// examples
//
// Created by David Rowe on 6 Mar 2015.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.load("progress.js");
Script.load("lobby.js");
Script.load("notifications.js");
Script.load("controllers/oculus/goTo.js");
//Script.load("scripts.js"); // Not created yet

View file

@ -2,6 +2,10 @@
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<script>
var PI = 3.14159265358979;
var DEGREES_TO_RADIANS = PI / 180.0;
var RADIANS_TO_DEGREES = 180.0 / PI;
function enableChildren(el, selector) {
els = el.querySelectorAll(selector);
for (var i = 0; i < els.length; i++) {
@ -58,6 +62,22 @@
}
};
function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) {
return function() {
var data = {
type: "update",
properties: {
}
};
data.properties[property] = {
x: elX.value * multiplier,
y: elY.value * multiplier,
z: elZ.value * multiplier,
};
EventBridge.emitWebEvent(JSON.stringify(data));
}
};
function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) {
return function() {
var data = {
@ -138,6 +158,7 @@
var elModelSections = document.querySelectorAll(".model-section");
var elModelURL = document.getElementById("property-model-url");
var elCollisionModelURL = document.getElementById("property-collision-model-url");
var elModelAnimationURL = document.getElementById("property-model-animation-url");
var elModelAnimationPlaying = document.getElementById("property-model-animation-playing");
var elModelAnimationFPS = document.getElementById("property-model-animation-fps");
@ -227,9 +248,9 @@
elLinearVelocityZ.value = properties.velocity.z.toFixed(2);
elLinearDamping.value = properties.damping.toFixed(2);
elAngularVelocityX.value = properties.angularVelocity.x.toFixed(2);
elAngularVelocityY.value = properties.angularVelocity.y.toFixed(2);
elAngularVelocityZ.value = properties.angularVelocity.z.toFixed(2);
elAngularVelocityX.value = (properties.angularVelocity.x * RADIANS_TO_DEGREES).toFixed(2);
elAngularVelocityY.value = (properties.angularVelocity.y * RADIANS_TO_DEGREES).toFixed(2);
elAngularVelocityZ.value = (properties.angularVelocity.z * RADIANS_TO_DEGREES).toFixed(2);
elAngularDamping.value = properties.angularDamping.toFixed(2);
elGravityX.value = properties.gravity.x.toFixed(2);
@ -267,6 +288,7 @@
}
elModelURL.value = properties.modelURL;
elCollisionModelURL.value = properties.collisionModelURL;
elModelAnimationURL.value = properties.animationURL;
elModelAnimationPlaying.checked = properties.animationIsPlaying;
elModelAnimationFPS.value = properties.animationFPS;
@ -352,8 +374,8 @@
elLinearVelocityZ.addEventListener('change', velocityChangeFunction);
elLinearDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('damping'));
var angularVelocityChangeFunction = createEmitVec3PropertyUpdateFunction(
'angularVelocity', elAngularVelocityX, elAngularVelocityY, elAngularVelocityZ);
var angularVelocityChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(
'angularVelocity', elAngularVelocityX, elAngularVelocityY, elAngularVelocityZ, DEGREES_TO_RADIANS);
elAngularVelocityX.addEventListener('change', angularVelocityChangeFunction);
elAngularVelocityY.addEventListener('change', angularVelocityChangeFunction);
elAngularVelocityZ.addEventListener('change', angularVelocityChangeFunction);
@ -391,6 +413,7 @@
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff'));
elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL'));
elCollisionModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('collisionModelURL'));
elModelAnimationURL.addEventListener('change', createEmitTextPropertyUpdateFunction('animationURL'));
elModelAnimationPlaying.addEventListener('change', createEmitCheckedPropertyUpdateFunction('animationIsPlaying'));
elModelAnimationFPS.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFPS'));
@ -622,6 +645,12 @@
<input type="text" id="property-model-url" class="url"></input>
</div>
</div>
<div class="model-section property">
<div class="label">Collision Model URL</div>
<div class="value">
<input type="text" id="property-collision-model-url" class="url"></input>
</div>
</div>
<div class="model-section property">
<div class="label">Animation URL</div>
<div class="value">

View file

@ -53,6 +53,7 @@ function Tooltip() {
text += "ID: " + properties.id + "\n"
if (properties.type == "Model") {
text += "Model URL: " + properties.modelURL + "\n"
text += "Collision Model URL: " + properties.collisionModelURL + "\n"
text += "Animation URL: " + properties.animationURL + "\n"
text += "Animation is playing: " + properties.animationIsPlaying + "\n"
if (properties.sittingPoints && properties.sittingPoints.length > 0) {

View file

@ -11,6 +11,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var DEGREES_TO_RADIANS = Math.PI / 180.0;
var RADIANS_TO_DEGREES = 180.0 / Math.PI;
EntityPropertyDialogBox = (function () {
var that = {};
@ -49,6 +52,8 @@ EntityPropertyDialogBox = (function () {
if (properties.type == "Model") {
array.push({ label: "Model URL:", value: properties.modelURL });
index++;
array.push({ label: "Collision Model URL:", value: properties.collisionModelURL });
index++;
array.push({ label: "Animation URL:", value: properties.animationURL });
index++;
array.push({ label: "Animation is playing:", type: "checkbox", value: properties.animationIsPlaying });
@ -146,11 +151,12 @@ EntityPropertyDialogBox = (function () {
index++;
array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
index++;
array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
// NOTE: angular velocity is in radians/sec but we display degrees/sec for users
array.push({ label: "Angular Pitch:", value: (properties.angularVelocity.x * RADIANS_TO_DEGREES).toFixed(decimals) });
index++;
array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) });
array.push({ label: "Angular Yaw:", value: (properties.angularVelocity.y * RADIANS_TO_DEGREES).toFixed(decimals) });
index++;
array.push({ label: "Angular Roll:", value: properties.angularVelocity.z.toFixed(decimals) });
array.push({ label: "Angular Roll:", value: (properties.angularVelocity.z * RADIANS_TO_DEGREES).toFixed(decimals) });
index++;
array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) });
index++;
@ -271,6 +277,7 @@ EntityPropertyDialogBox = (function () {
properties.locked = array[index++].value;
if (properties.type == "Model") {
properties.modelURL = array[index++].value;
properties.collisionModelURL = array[index++].value;
properties.animationURL = array[index++].value;
var newAnimationIsPlaying = array[index++].value;
@ -343,9 +350,10 @@ EntityPropertyDialogBox = (function () {
properties.velocity.z = array[index++].value;
properties.damping = array[index++].value;
properties.angularVelocity.x = array[index++].value;
properties.angularVelocity.y = array[index++].value;
properties.angularVelocity.z = array[index++].value;
// NOTE: angular velocity is in radians/sec but we display degrees/sec for users
properties.angularVelocity.x = array[index++].value * DEGREES_TO_RADIANS;
properties.angularVelocity.y = array[index++].value * DEGREES_TO_RADIANS;
properties.angularVelocity.z = array[index++].value * DEGREES_TO_RADIANS;
properties.angularDamping = array[index++].value;
properties.gravity.x = array[index++].value;

View file

@ -35,13 +35,15 @@ var NUM_INITIAL_PARTICLES = 200;
var PARTICLE_MIN_SIZE = 0.50;
var PARTICLE_MAX_SIZE = 1.50;
var INITIAL_VELOCITY = 5.0;
var DEGREES_TO_RADIANS = Math.PI / 180.0;
var planets = [];
var particles = [];
// Create planets that will extert gravity on test particles
for (var i = 0; i < planetTypes.length; i++) {
var rotationalVelocity = 10 + Math.random() * 60;
// NOTE: rotationalVelocity is in radians/sec
var rotationalVelocity = (10 + Math.random() * 60) * DEGREES_TO_RADIANS;
var position = { x: planetTypes[i].x, y: planetTypes[i].y, z: planetTypes[i].z };
position = Vec3.multiply(MAX_RANGE / 2, position);
position = Vec3.sum(center, position);
@ -118,4 +120,4 @@ function update(deltaTime) {
}
}
Script.scriptEnding.connect(scriptEnding);
Script.scriptEnding.connect(scriptEnding);

View file

@ -20,6 +20,8 @@ var GRAVITY = -1.0;
var LIFETIME = 600;
var DAMPING = 0.50;
var TWO_PI = 2.0 * Math.PI;
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(SCALE * 3.0, Quat.getFront(Camera.getOrientation())));
var floor = Entities.addEntity(
@ -122,7 +124,8 @@ var spinner = Entities.addEntity(
position: center,
dimensions: { x: SCALE / 1.5, y: SCALE / 3.0, z: SCALE / 8.0 },
color: { red: 255, green: 0, blue: 0 },
angularVelocity: { x: 0, y: 360, z: 0 },
// NOTE: angularVelocity is in radians/sec
angularVelocity: { x: 0, y: TWO_PI, z: 0 },
angularDamping: 0.0,
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: false,
@ -179,4 +182,4 @@ function scriptEnding() {
}
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(update);
Script.update.connect(update);

View file

@ -849,4 +849,4 @@ function animateAvatar(deltaTime, speed) {
MyAvatar.setJointData(jointName, Quat.fromVec3Degrees(jointRotations));
}
}
}
}

View file

@ -87,6 +87,7 @@
#include "Application.h"
#include "AudioClient.h"
#include "DiscoverabilityManager.h"
#include "InterfaceVersion.h"
#include "LODManager.h"
#include "Menu.h"
@ -141,7 +142,7 @@ using namespace std;
static unsigned STARFIELD_NUM_STARS = 50000;
static unsigned STARFIELD_SEED = 1;
const qint64 MAXIMUM_CACHE_SIZE = 10737418240; // 10GB
const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB
static QTimer* locationUpdateTimer = NULL;
static QTimer* balanceUpdateTimer = NULL;
@ -151,7 +152,7 @@ static QTimer* billboardPacketTimer = NULL;
static QTimer* checkFPStimer = NULL;
static QTimer* idleTimer = NULL;
const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml";
const QString CHECK_VERSION_URL = "https://highfidelity.com/latestVersion.xml";
const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion";
const QString DEFAULT_SCRIPTS_JS_URL = "http://s3.amazonaws.com/hifi-public/scripts/defaultScripts.js";
@ -246,6 +247,7 @@ bool setupEssentials(int& argc, char** argv) {
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
auto speechRecognizer = DependencyManager::set<SpeechRecognizer>();
#endif
auto discoverabilityManager = DependencyManager::set<DiscoverabilityManager>();
return true;
}
@ -367,11 +369,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(&domainHandler, &DomainHandler::hostnameChanged,
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
locationUpdateTimer = new QTimer(this);
connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer);
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
connect(locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded);
@ -445,7 +448,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
networkAccessManager.setCache(cache);
ResourceCache::setRequestLimit(3);
_window->setCentralWidget(_glWidget);
@ -490,6 +493,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(nodeList.data(), SIGNAL(dataReceived(const quint8, const int)),
bandwidthRecorder.data(), SLOT(updateInboundData(const quint8, const int)));
connect(&_myAvatar->getSkeletonModel(), &SkeletonModel::skeletonLoaded,
this, &Application::checkSkeleton, Qt::QueuedConnection);
// check first run...
if (_firstRun.get()) {
qDebug() << "This is a first run...";
@ -1140,6 +1146,10 @@ void Application::keyPressEvent(QKeyEvent* event) {
break;
}
case Qt::Key_Comma: {
_myAvatar->togglePhysicsEnabled();
}
default:
event->ignore();
@ -1673,8 +1683,7 @@ bool Application::exportEntities(const QString& filename, const QVector<EntityIt
bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) {
QVector<EntityItem*> entities;
_entities.getTree()->findEntities(AACube(glm::vec3(x / (float)TREE_SCALE,
y / (float)TREE_SCALE, z / (float)TREE_SCALE), scale / (float)TREE_SCALE), entities);
_entities.getTree()->findEntities(AACube(glm::vec3(x, y, z), scale), entities);
if (entities.size() > 0) {
glm::vec3 root(x, y, z);
@ -1805,6 +1814,9 @@ void Application::init() {
tree->setSimulation(&_physicsEngine);
_physicsEngine.init(&_entityEditSender);
_physicsEngine.setAvatarData(_myAvatar);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity,
@ -2301,7 +2313,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverBounds.scale(TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
@ -2365,7 +2376,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverBounds.scale(TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
@ -3184,45 +3194,6 @@ void Application::updateWindowTitle(){
_window->setWindowTitle(title);
}
void Application::updateLocationInServer() {
AccountManager& accountManager = AccountManager::getInstance();
auto addressManager = DependencyManager::get<AddressManager>();
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
if (accountManager.isLoggedIn() && domainHandler.isConnected()
&& (!addressManager->getRootPlaceID().isNull() || !domainHandler.getUUID().isNull())) {
// construct a QJsonObject given the user's current address information
QJsonObject rootObject;
QJsonObject locationObject;
QString pathString = addressManager->currentPath();
const QString LOCATION_KEY_IN_ROOT = "location";
const QString PATH_KEY_IN_LOCATION = "path";
locationObject.insert(PATH_KEY_IN_LOCATION, pathString);
if (!addressManager->getRootPlaceID().isNull()) {
const QString PLACE_ID_KEY_IN_LOCATION = "place_id";
locationObject.insert(PLACE_ID_KEY_IN_LOCATION,
uuidStringWithoutCurlyBraces(addressManager->getRootPlaceID()));
} else {
const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id";
locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION,
uuidStringWithoutCurlyBraces(domainHandler.getUUID()));
}
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), QJsonDocument(rootObject).toJson());
}
}
void Application::clearDomainOctreeDetails() {
qDebug() << "Clearing domain octree details...";
// reset the environment so that we don't erroneously end up with multiple
@ -3908,8 +3879,8 @@ void Application::takeSnapshot() {
}
void Application::setVSyncEnabled() {
bool vsyncOn = Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn);
#if defined(Q_OS_WIN)
bool vsyncOn = Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn);
if (wglewGetExtension("WGL_EXT_swap_control")) {
wglSwapIntervalEXT(vsyncOn);
int swapInterval = wglGetSwapIntervalEXT();
@ -3932,7 +3903,6 @@ void Application::setVSyncEnabled() {
#else
qDebug("V-Sync is FORCED ON on this system\n");
#endif
vsyncOn = true; // Turns off unused variable warning
}
bool Application::isVSyncOn() const {
@ -4044,3 +4014,19 @@ void Application::notifyPacketVersionMismatch() {
msgBox.exec();
}
}
void Application::checkSkeleton() {
if (_myAvatar->getSkeletonModel().isActive() && !_myAvatar->getSkeletonModel().hasSkeleton()) {
qDebug() << "MyAvatar model has no skeleton";
QString message = "Your selected avatar body has no skeleton.\n\nThe default body will be loaded...";
QMessageBox msgBox;
msgBox.setText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Warning);
msgBox.exec();
_myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL);
_myAvatar->sendIdentityPacket();
}
}

View file

@ -318,7 +318,6 @@ signals:
public slots:
void domainChanged(const QString& domainHostname);
void updateWindowTitle();
void updateLocationInServer();
void nodeAdded(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
void packetSent(quint64 length);
@ -585,6 +584,8 @@ private:
QTimer _settingsTimer;
GLCanvas* _glWidget = new GLCanvas(); // our GLCanvas has a couple extra features
void checkSkeleton();
};
#endif // hifi_Application_h

View file

@ -0,0 +1,88 @@
//
// DiscoverabilityManager.cpp
// interface/src
//
// Created by Stephen Birarda on 2015-03-09.
// 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 <QtCore/QJsonDocument>
#include <AccountManager.h>
#include <AddressManager.h>
#include <DomainHandler.h>
#include <NodeList.h>
#include <UUID.h>
#include "DiscoverabilityManager.h"
const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::All;
DiscoverabilityManager::DiscoverabilityManager() :
_mode("discoverabilityMode", DEFAULT_DISCOVERABILITY_MODE)
{
}
const QString API_USER_LOCATION_PATH = "/api/v1/user/location";
void DiscoverabilityManager::updateLocation() {
if (_mode.get() != Discoverability::None) {
AccountManager& accountManager = AccountManager::getInstance();
auto addressManager = DependencyManager::get<AddressManager>();
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
if (accountManager.isLoggedIn() && domainHandler.isConnected()
&& (!addressManager->getRootPlaceID().isNull() || !domainHandler.getUUID().isNull())) {
// construct a QJsonObject given the user's current address information
QJsonObject rootObject;
QJsonObject locationObject;
QString pathString = addressManager->currentPath();
const QString LOCATION_KEY_IN_ROOT = "location";
const QString PATH_KEY_IN_LOCATION = "path";
locationObject.insert(PATH_KEY_IN_LOCATION, pathString);
if (!addressManager->getRootPlaceID().isNull()) {
const QString PLACE_ID_KEY_IN_LOCATION = "place_id";
locationObject.insert(PLACE_ID_KEY_IN_LOCATION,
uuidStringWithoutCurlyBraces(addressManager->getRootPlaceID()));
} else {
const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id";
locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION,
uuidStringWithoutCurlyBraces(domainHandler.getUUID()));
}
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
accountManager.authenticatedRequest(API_USER_LOCATION_PATH, QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), QJsonDocument(rootObject).toJson());
}
}
}
void DiscoverabilityManager::removeLocation() {
AccountManager& accountManager = AccountManager::getInstance();
accountManager.authenticatedRequest(API_USER_LOCATION_PATH, QNetworkAccessManager::DeleteOperation);
}
void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discoverabilityMode) {
if (static_cast<Discoverability::Mode>(_mode.get()) != discoverabilityMode) {
// update the setting to the new value
_mode.set(static_cast<int>(discoverabilityMode));
if (static_cast<int>(_mode.get()) == Discoverability::None) {
// if we just got set to no discoverability, make sure that we delete our location in DB
removeLocation();
}
}
}

View file

@ -0,0 +1,45 @@
//
// DiscoverabilityManager.h
// interface/src
//
// Created by Stephen Birarda on 2015-03-09.
// 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_DiscoverabilityManager_h
#define hifi_DiscoverabilityManager_h
#include <DependencyManager.h>
#include <SettingHandle.h>
namespace Discoverability {
enum Mode {
None,
Friends,
All
};
}
Q_DECLARE_METATYPE(Discoverability::Mode);
class DiscoverabilityManager : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public slots:
void updateLocation();
void removeLocation();
Discoverability::Mode getDiscoverabilityMode() { return static_cast<Discoverability::Mode>(_mode.get()); }
void setDiscoverabilityMode(Discoverability::Mode discoverabilityMode);
private:
DiscoverabilityManager();
Setting::Handle<int> _mode;
};
#endif // hifi_DiscoverabilityManager_h

View file

@ -436,6 +436,8 @@ Menu::Menu() {
SLOT(disable(bool)));
addActionToQMenuAndActionHash(networkMenu, MenuOption::CachesSize, 0,
dialogsManager.data(), SLOT(cachesSizeDialog()));
addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0,
dialogsManager.data(), SLOT(toggleDiskCacheEditor()));
QMenu* timingMenu = developerMenu->addMenu("Timing and Stats");
QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer");

View file

@ -125,7 +125,7 @@ namespace MenuOption {
const QString BookmarkLocation = "Bookmark Location";
const QString Bookmarks = "Bookmarks";
const QString CascadedShadows = "Cascaded";
const QString CachesSize = "Caches Size";
const QString CachesSize = "RAM Caches Size";
const QString Chat = "Chat...";
const QString ChatCircling = "Chat Circling";
const QString CollideAsRagdoll = "Collide With Self (Ragdoll)";
@ -143,6 +143,7 @@ namespace MenuOption {
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
const QString DisableLightEntities = "Disable Light Entities";
const QString DisableNackPackets = "Disable NACK Packets";
const QString DiskCacheEditor = "Disk Cache Editor";
const QString DisplayHands = "Show Hand Info";
const QString DisplayHandTargets = "Show Hand Targets";
const QString DisplayModelBounds = "Display Model Bounds";

View file

@ -177,6 +177,24 @@ bool ModelUploader::zip() {
}
QByteArray fbxContents = fbx.readAll();
FBXGeometry geometry = readFBX(fbxContents, QVariantHash());
#if 0 /// Temporarily remove this check until CtrlAltDavid can come up with a fix.
// Make sure that a skeleton model has a skeleton
if (_modelType == SKELETON_MODEL) {
if (geometry.rootJointIndex == -1) {
QString message = "Your selected skeleton model has no skeleton.\n\nThe upload will be canceled.";
QMessageBox msgBox;
msgBox.setWindowTitle("Model Upload");
msgBox.setText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Warning);
msgBox.exec();
return false;
}
}
#endif
// make sure we have some basic mappings
populateBasicMapping(mapping, filename, geometry);
@ -314,14 +332,15 @@ void ModelUploader::populateBasicMapping(QVariantHash& mapping, QString filename
// mixamo blendshapes - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
bool likelyMixamoFile = geometry.applicationName == "mixamo.com" ||
(geometry.blendshapeChannelNames.contains("BrowsDown_Left") &&
geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
(geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
geometry.blendshapeChannelNames.contains("MouthOpen") &&
geometry.blendshapeChannelNames.contains("TongueUp") &&
geometry.blendshapeChannelNames.contains("MouthWhistle_NarrowAdjust_Left") &&
geometry.blendshapeChannelNames.contains("NoseScrunch_Left") &&
geometry.blendshapeChannelNames.contains("Blink_Left") &&
geometry.blendshapeChannelNames.contains("Blink_Right") &&
geometry.blendshapeChannelNames.contains("Squint_Right"));
qDebug() << "likelyMixamoFile:" << likelyMixamoFile;
qDebug() << "geometry.blendshapeChannelNames:" << geometry.blendshapeChannelNames;
if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) {
QVariantHash blendshapes;
blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);

View file

@ -63,7 +63,6 @@ Avatar::Avatar() :
_skeletonModel(this),
_skeletonOffset(0.0f),
_bodyYawDelta(0.0f),
_velocity(0.0f),
_positionDeltaAccumulator(0.0f),
_lastVelocity(0.0f),
_acceleration(0.0f),

View file

@ -155,7 +155,6 @@ public:
Q_INVOKABLE glm::vec3 getNeckPosition() const;
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; }
Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; }
@ -184,11 +183,9 @@ protected:
QVector<Model*> _attachmentModels;
float _bodyYawDelta;
glm::vec3 _velocity;
// These position histories and derivatives are in the world-frame.
// The derivatives are the MEASURED results of all external and internal forces
// and are therefor READ-ONLY --> motion control of the Avatar is NOT obtained
// and are therefore READ-ONLY --> motion control of the Avatar is NOT obtained
// by setting these values.
// Floating point error prevents us from accurately measuring velocity using a naive approach
// (e.g. vel = (pos - lastPos)/dt) so instead we use _positionDeltaAccumulator.

View file

@ -21,7 +21,6 @@ ModelReferential::ModelReferential(Referential* referential, EntityTree* tree, A
{
_translation = referential->getTranslation();
_rotation = referential->getRotation();
_scale = referential->getScale();
unpackExtraData(reinterpret_cast<unsigned char*>(referential->getExtraData().data()),
referential->getExtraData().size());
@ -32,9 +31,9 @@ ModelReferential::ModelReferential(Referential* referential, EntityTree* tree, A
const EntityItem* item = _tree->findEntityByID(_entityID);
if (item != NULL) {
_refScale = item->getLargestDimension();
_lastRefDimension = item->getDimensions();
_refRotation = item->getRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
_refPosition = item->getPosition();
update();
}
}
@ -51,14 +50,13 @@ ModelReferential::ModelReferential(const QUuid& entityID, EntityTree* tree, Avat
return;
}
_refScale = item->getLargestDimension();
_lastRefDimension = item->getDimensions();
_refRotation = item->getRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
_refPosition = item->getPosition();
glm::quat refInvRot = glm::inverse(_refRotation);
_scale = _avatar->getTargetScale() / _refScale;
_rotation = refInvRot * _avatar->getOrientation();
_translation = refInvRot * (avatar->getPosition() - _refPosition) / _refScale;
_translation = refInvRot * (avatar->getPosition() - _refPosition);
}
void ModelReferential::update() {
@ -68,9 +66,10 @@ void ModelReferential::update() {
}
bool somethingChanged = false;
if (item->getLargestDimension() != _refScale) {
_refScale = item->getLargestDimension();
_avatar->setTargetScale(_refScale * _scale, true);
if (item->getDimensions() != _lastRefDimension) {
glm::vec3 oldDimension = _lastRefDimension;
_lastRefDimension = item->getDimensions();
_translation *= _lastRefDimension / oldDimension;
somethingChanged = true;
}
if (item->getRotation() != _refRotation) {
@ -80,7 +79,7 @@ void ModelReferential::update() {
}
if (item->getPosition() != _refPosition || somethingChanged) {
_refPosition = item->getPosition();
_avatar->setPosition(_refPosition * (float)TREE_SCALE + _refRotation * (_translation * _refScale), true);
_avatar->setPosition(_refPosition + _refRotation * _translation, true);
}
}
@ -108,7 +107,7 @@ JointReferential::JointReferential(Referential* referential, EntityTree* tree, A
const EntityItem* item = _tree->findEntityByID(_entityID);
const Model* model = getModel(item);
if (!isValid() || model == NULL || _jointIndex >= (uint32_t)(model->getJointStateCount())) {
_refScale = item->getLargestDimension();
_lastRefDimension = item->getDimensions();
model->getJointRotationInWorldFrame(_jointIndex, _refRotation);
model->getJointPositionInWorldFrame(_jointIndex, _refPosition);
}
@ -128,14 +127,14 @@ JointReferential::JointReferential(uint32_t jointIndex, const QUuid& entityID, E
return;
}
_refScale = item->getLargestDimension();
_lastRefDimension = item->getDimensions();
model->getJointRotationInWorldFrame(_jointIndex, _refRotation);
model->getJointPositionInWorldFrame(_jointIndex, _refPosition);
glm::quat refInvRot = glm::inverse(_refRotation);
_scale = _avatar->getTargetScale() / _refScale;
_rotation = refInvRot * _avatar->getOrientation();
_translation = refInvRot * (avatar->getPosition() - _refPosition) / _refScale;
// BUG! _refPosition is in domain units, but avatar is in meters
_translation = refInvRot * (avatar->getPosition() - _refPosition);
}
void JointReferential::update() {
@ -146,9 +145,10 @@ void JointReferential::update() {
}
bool somethingChanged = false;
if (item->getLargestDimension() != _refScale) {
_refScale = item->getLargestDimension();
_avatar->setTargetScale(_refScale * _scale, true);
if (item->getDimensions() != _lastRefDimension) {
glm::vec3 oldDimension = _lastRefDimension;
_lastRefDimension = item->getDimensions();
_translation *= _lastRefDimension / oldDimension;
somethingChanged = true;
}
if (item->getRotation() != _refRotation) {
@ -158,7 +158,7 @@ void JointReferential::update() {
}
if (item->getPosition() != _refPosition || somethingChanged) {
model->getJointPositionInWorldFrame(_jointIndex, _refPosition);
_avatar->setPosition(_refPosition + _refRotation * (_translation * _refScale), true);
_avatar->setPosition(_refPosition + _refRotation * _translation, true);
}
}

View file

@ -45,4 +45,4 @@ protected:
uint32_t _jointIndex;
};
#endif // hifi_ModelReferential_h
#endif // hifi_ModelReferential_h

View file

@ -12,7 +12,6 @@
#include <algorithm>
#include <vector>
#include <QMessageBox>
#include <QBuffer>
#include <glm/gtx/norm.hpp>
@ -182,7 +181,11 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("transform");
updateOrientation(deltaTime);
updatePosition(deltaTime);
if (isPhysicsEnabled()) {
updatePositionWithPhysics(deltaTime);
} else {
updatePosition(deltaTime);
}
}
{
@ -195,6 +198,12 @@ void MyAvatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("skeleton");
_skeletonModel.simulate(deltaTime);
}
if (!_skeletonModel.hasSkeleton()) {
// All the simulation that can be done has been done
return;
}
{
PerformanceTimer perfTimer("attachments");
simulateAttachments(deltaTime);
@ -1392,6 +1401,25 @@ void MyAvatar::updatePosition(float deltaTime) {
measureMotionDerivatives(deltaTime);
}
void MyAvatar::updatePositionWithPhysics(float deltaTime) {
// rotate velocity into camera frame
glm::quat rotation = getHead()->getCameraOrientation();
glm::vec3 localVelocity = glm::inverse(rotation) * _velocity;
bool hasFloor = false;
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor);
newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity);
// cap avatar speed
float speed = glm::length(newLocalVelocity);
if (speed > MAX_WALKING_SPEED) {
newLocalVelocity *= MAX_WALKING_SPEED / speed;
}
// rotate back into world-frame
_velocity = rotation * newLocalVelocity;
}
void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
glm::vec3 up = getBodyUpDirection();
const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f;

View file

@ -164,8 +164,6 @@ public slots:
glm::vec3 getThrust() { return _thrust; };
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
void updateMotionBehavior();
void onToggleRagdoll();
@ -233,6 +231,7 @@ private:
glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool walkingOnFloor);
glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity);
void updatePosition(float deltaTime);
void updatePositionWithPhysics(float deltaTime);
void updateCollisionWithAvatars(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime, float radius);
void updateCollisionWithVoxels(float deltaTime, float radius);

View file

@ -81,6 +81,8 @@ void SkeletonModel::setJointStates(QVector<JointState> states) {
if (_enableShapes) {
buildShapes();
}
emit skeletonLoaded();
}
const float PALM_PRIORITY = DEFAULT_PRIORITY;
@ -721,7 +723,8 @@ void SkeletonModel::buildShapes() {
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.joints.isEmpty()) {
if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) {
// rootJointIndex == -1 if the avatar model has no skeleton
return;
}
@ -1006,3 +1009,6 @@ void SkeletonModel::renderJointCollisionShapes(float alpha) {
glPopMatrix();
}
bool SkeletonModel::hasSkeleton() {
return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false;
}

View file

@ -123,7 +123,13 @@ public:
void resetShapePositionsToDefaultPose(); // DEBUG method
void renderRagdoll();
bool hasSkeleton();
signals:
void skeletonLoaded();
protected:
void buildShapes();

View file

@ -42,7 +42,7 @@ void OctreeFade::render() {
glDisable(GL_LIGHTING);
glPushMatrix();
glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE);
glScalef(1.0f, 1.0f, 1.0f);
glTranslatef(voxelDetails.x + voxelDetails.s * 0.5f,
voxelDetails.y + voxelDetails.s * 0.5f,
voxelDetails.z + voxelDetails.s * 0.5f);

View file

@ -1,4 +1,4 @@
//
//Merge branch 'master' of ssh://github.com/highfidelity/hifi into isentropic/
// OctreePacketProcessor.cpp
// interface/src/octree
//

View file

@ -11,6 +11,7 @@
#include "AccountManager.h"
#include "Application.h"
#include "DiscoverabilityManager.h"
#include "ResourceCache.h"
#include "GlobalServicesScriptingInterface.h"
@ -36,7 +37,7 @@ GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance(
return &sharedInstance;
}
QString GlobalServicesScriptingInterface::getMyUsername() {
const QString& GlobalServicesScriptingInterface::getUsername() const {
return AccountManager::getInstance().getAccountInfo().getUsername();
}
@ -44,6 +45,32 @@ void GlobalServicesScriptingInterface::loggedOut() {
emit GlobalServicesScriptingInterface::disconnected(QString("logout"));
}
QString GlobalServicesScriptingInterface::getFindableBy() const {
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
if (discoverabilityManager->getDiscoverabilityMode() == Discoverability::None) {
return "none";
} else if (discoverabilityManager->getDiscoverabilityMode() == Discoverability::Friends) {
return "friends";
} else {
return "all";
}
}
void GlobalServicesScriptingInterface::setFindableBy(const QString& discoverabilityMode) {
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
if (discoverabilityMode.toLower() == "none") {
discoverabilityManager->setDiscoverabilityMode(Discoverability::None);
} else if (discoverabilityMode.toLower() == "friends") {
discoverabilityManager->setDiscoverabilityMode(Discoverability::Friends);
} else if (discoverabilityMode.toLower() == "all") {
discoverabilityManager->setDiscoverabilityMode(Discoverability::All);
} else {
qDebug() << "GlobalServices setFindableBy called with an unrecognized value. Did not change discoverability.";
}
}
DownloadInfoResult::DownloadInfoResult() :
downloading(QList<float>()),
pending(0.0f)

View file

@ -31,16 +31,16 @@ Q_DECLARE_METATYPE(DownloadInfoResult)
QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result);
void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoResult& result);
class GlobalServicesScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(QString myUsername READ getMyUsername)
GlobalServicesScriptingInterface();
~GlobalServicesScriptingInterface();
Q_PROPERTY(QString username READ getUsername)
Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy)
public:
static GlobalServicesScriptingInterface* getInstance();
QString getMyUsername();
const QString& getUsername() const;
public slots:
DownloadInfoResult getDownloadInfo();
@ -49,16 +49,20 @@ public slots:
private slots:
void loggedOut();
void checkDownloadInfo();
QString getFindableBy() const;
void setFindableBy(const QString& discoverabilityMode);
signals:
void connected();
void disconnected(const QString& reason);
void incomingMessage(const QString& username, const QString& message);
void onlineUsersChanged(const QStringList& usernames);
void myUsernameChanged(const QString& username);
void downloadInfoChanged(DownloadInfoResult info);
private:
GlobalServicesScriptingInterface();
~GlobalServicesScriptingInterface();
bool _downloading;
};

View file

@ -20,6 +20,7 @@
#include "AttachmentsDialog.h"
#include "BandwidthDialog.h"
#include "CachesSizeDialog.h"
#include "DiskCacheEditor.h"
#include "HMDToolsDialog.h"
#include "LodToolsDialog.h"
#include "LoginDialog.h"
@ -37,6 +38,11 @@ void DialogsManager::toggleAddressBar() {
}
}
void DialogsManager::toggleDiskCacheEditor() {
maybeCreateDialog(_diskCacheEditor);
_diskCacheEditor->toggle();
}
void DialogsManager::toggleLoginDialog() {
maybeCreateDialog(_loginDialog);
_loginDialog->toggleQAction();
@ -61,7 +67,6 @@ void DialogsManager::octreeStatsDetails() {
}
void DialogsManager::cachesSizeDialog() {
qDebug() << "Caches size:" << _cachesSizeDialog.isNull();
if (!_cachesSizeDialog) {
maybeCreateDialog(_cachesSizeDialog);

View file

@ -24,8 +24,9 @@ class QAction;
class AddressBarDialog;
class AnimationsDialog;
class AttachmentsDialog;
class CachesSizeDialog;
class BandwidthDialog;
class CachesSizeDialog;
class DiskCacheEditor;
class LodToolsDialog;
class LoginDialog;
class OctreeStatsDialog;
@ -45,6 +46,7 @@ public:
public slots:
void toggleAddressBar();
void toggleDiskCacheEditor();
void toggleLoginDialog();
void showLoginDialog();
void octreeStatsDetails();
@ -73,7 +75,7 @@ private:
member = new T(parent);
Q_CHECK_PTR(member);
if (_hmdToolsDialog) {
if (_hmdToolsDialog && member->windowHandle()) {
_hmdToolsDialog->watchWindow(member->windowHandle());
}
}
@ -84,6 +86,7 @@ private:
QPointer<AttachmentsDialog> _attachmentsDialog;
QPointer<BandwidthDialog> _bandwidthDialog;
QPointer<CachesSizeDialog> _cachesSizeDialog;
QPointer<DiskCacheEditor> _diskCacheEditor;
QPointer<QMessageBox> _ircInfoBox;
QPointer<HMDToolsDialog> _hmdToolsDialog;
QPointer<LodToolsDialog> _lodToolsDialog;

View file

@ -0,0 +1,150 @@
//
// DiskCacheEditor.cpp
//
//
// Created by Clement on 3/4/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 <functional>
#include <QDebug>
#include <QDialog>
#include <QGridLayout>
#include <QPushButton>
#include <QLabel>
#include <QNetworkDiskCache>
#include <QMessageBox>
#include <NetworkAccessManager.h>
#include "DiskCacheEditor.h"
DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) {
}
QWindow* DiskCacheEditor::windowHandle() {
return (_dialog) ? _dialog->windowHandle() : nullptr;
}
void DiskCacheEditor::toggle() {
qDebug() << "DiskCacheEditor::toggle()";
if (!_dialog) {
makeDialog();
}
if (!_dialog->isActiveWindow()) {
_dialog->show();
_dialog->raise();
_dialog->activateWindow();
} else {
_dialog->close();
}
}
void DiskCacheEditor::makeDialog() {
_dialog = new QDialog(static_cast<QWidget*>(parent()));
Q_CHECK_PTR(_dialog);
_dialog->setAttribute(Qt::WA_DeleteOnClose);
_dialog->setWindowTitle("Disk Cache Editor");
QGridLayout* layout = new QGridLayout(_dialog);
Q_CHECK_PTR(layout);
_dialog->setLayout(layout);
QLabel* path = new QLabel("Path : ", _dialog);
Q_CHECK_PTR(path);
path->setAlignment(Qt::AlignRight);
layout->addWidget(path, 0, 0);
QLabel* size = new QLabel("Current Size : ", _dialog);
Q_CHECK_PTR(size);
size->setAlignment(Qt::AlignRight);
layout->addWidget(size, 1, 0);
QLabel* maxSize = new QLabel("Max Size : ", _dialog);
Q_CHECK_PTR(maxSize);
maxSize->setAlignment(Qt::AlignRight);
layout->addWidget(maxSize, 2, 0);
_path = new QLabel(_dialog);
Q_CHECK_PTR(_path);
_path->setAlignment(Qt::AlignLeft);
layout->addWidget(_path, 0, 1, 1, 3);
_size = new QLabel(_dialog);
Q_CHECK_PTR(_size);
_size->setAlignment(Qt::AlignLeft);
layout->addWidget(_size, 1, 1, 1, 3);
_maxSize = new QLabel(_dialog);
Q_CHECK_PTR(_maxSize);
_maxSize->setAlignment(Qt::AlignLeft);
layout->addWidget(_maxSize, 2, 1, 1, 3);
refresh();
QPushButton* refreshCacheButton = new QPushButton(_dialog);
Q_CHECK_PTR(refreshCacheButton);
refreshCacheButton->setText("Refresh");
refreshCacheButton->setToolTip("Reload the cache stats.");
connect(refreshCacheButton, SIGNAL(clicked()), SLOT(refresh()));
layout->addWidget(refreshCacheButton, 3, 2);
QPushButton* clearCacheButton = new QPushButton(_dialog);
Q_CHECK_PTR(clearCacheButton);
clearCacheButton->setText("Clear");
clearCacheButton->setToolTip("Erases the entire content of the disk cache.");
connect(clearCacheButton, SIGNAL(clicked()), SLOT(clear()));
layout->addWidget(clearCacheButton, 3, 3);
}
void DiskCacheEditor::refresh() {
static const std::function<QString(qint64)> stringify = [](qint64 number) {
static const QStringList UNITS = QStringList() << "B" << "KB" << "MB" << "GB";
static const qint64 CHUNK = 1024;
QString unit;
int i = 0;
for (i = 0; i < 4; ++i) {
if (number / CHUNK > 0) {
number /= CHUNK;
} else {
break;
}
}
return QString("%0 %1").arg(number).arg(UNITS[i]);
};
QNetworkDiskCache* cache = qobject_cast<QNetworkDiskCache*>(NetworkAccessManager::getInstance().cache());
if (_path) {
_path->setText(cache->cacheDirectory());
}
if (_size) {
_size->setText(stringify(cache->cacheSize()));
}
if (_maxSize) {
_maxSize->setText(stringify(cache->maximumCacheSize()));
}
}
void DiskCacheEditor::clear() {
QMessageBox::StandardButton buttonClicked =
QMessageBox::question(_dialog, "Clearing disk cache",
"You are about to erase all the content of the disk cache,"
"are you sure you want to do that?");
if (buttonClicked == QMessageBox::Yes) {
QNetworkDiskCache* cache = qobject_cast<QNetworkDiskCache*>(NetworkAccessManager::getInstance().cache());
if (cache) {
qDebug() << "DiskCacheEditor::clear(): Clearing disk cache.";
cache->clear();
}
}
refresh();
}

View file

@ -0,0 +1,46 @@
//
// DiskCacheEditor.h
//
//
// Created by Clement on 3/4/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_DiskCacheEditor_h
#define hifi_DiskCacheEditor_h
#include <QObject>
#include <QPointer>
class QDialog;
class QLabel;
class QWindow;
class DiskCacheEditor : public QObject {
Q_OBJECT
public:
DiskCacheEditor(QWidget* parent = nullptr);
QWindow* windowHandle();
public slots:
void toggle();
private slots:
void refresh();
void clear();
private:
void makeDialog();
QPointer<QDialog> _dialog;
QPointer<QLabel> _path;
QPointer<QLabel> _size;
QPointer<QLabel> _maxSize;
};
#endif // hifi_DiskCacheEditor_h

View file

@ -71,20 +71,19 @@ void NodeBounds::draw() {
voxelDetailsForCode(rootCode, rootDetails);
serverJurisdictions->unlock();
glm::vec3 location(rootDetails.x, rootDetails.y, rootDetails.z);
location *= (float)TREE_SCALE;
AACube serverBounds(location, rootDetails.s * TREE_SCALE);
AACube serverBounds(location, rootDetails.s);
glm::vec3 center = serverBounds.getVertex(BOTTOM_RIGHT_NEAR)
+ ((serverBounds.getVertex(TOP_LEFT_FAR) - serverBounds.getVertex(BOTTOM_RIGHT_NEAR)) / 2.0f);
const float ENTITY_NODE_SCALE = 0.99f;
float scaleFactor = rootDetails.s * TREE_SCALE;
float scaleFactor = rootDetails.s;
// Scale by 0.92 - 1.00 depending on the scale of the node. This allows smaller nodes to scale in
// a bit and not overlap larger nodes.
scaleFactor *= 0.92 + (rootDetails.s * 0.08);
scaleFactor *= 0.92f + (rootDetails.s * 0.08f);
// Scale different node types slightly differently because it's common for them to overlap.
if (nodeType == NodeType::EntityServer) {

View file

@ -273,7 +273,6 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverBounds.scale(TREE_SCALE);
serverDetails << " jurisdiction: "
<< qPrintable(rootCodeHex)
<< " ["

View file

@ -55,7 +55,8 @@ AvatarData::AvatarData() :
_billboard(),
_errorLogExpiry(0),
_owningAvatarMixer(),
_lastUpdateTimer()
_lastUpdateTimer(),
_velocity(0.0f)
{
}

View file

@ -43,6 +43,7 @@ typedef unsigned long long quint64;
#include <QVariantMap>
#include <QVector>
#include <QtScript/QScriptable>
#include <QReadWriteLock>
#include <CollisionInfo.h>
#include <RegisteredMetaTypes.h>
@ -300,6 +301,19 @@ public:
const Referential* getReferential() const { return _referential; }
void togglePhysicsEnabled() { _enablePhysics = !_enablePhysics; }
bool isPhysicsEnabled() { return _enablePhysics; }
void setPhysicsEnabled(bool enablePhysics) { _enablePhysics = enablePhysics; }
void lockForRead() { _lock.lockForRead(); }
bool tryLockForRead() { return _lock.tryLockForRead(); }
void lockForWrite() { _lock.lockForWrite(); }
bool tryLockForWrite() { return _lock.tryLockForWrite(); }
void unlock() { _lock.unlock(); }
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
public slots:
void sendAvatarDataPacket();
void sendIdentityPacket();
@ -389,10 +403,15 @@ protected:
virtual void updateJointMappings();
void changeReferential(Referential* ref);
glm::vec3 _velocity;
private:
// privatize the copy constructor and assignment operator so they cannot be called
AvatarData(const AvatarData&);
AvatarData& operator= (const AvatarData&);
QReadWriteLock _lock;
bool _enablePhysics = false;
};
Q_DECLARE_METATYPE(AvatarData*)

View file

@ -76,7 +76,6 @@ int Referential::pack(unsigned char* destinationBuffer) const {
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, _translation, 0);
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _rotation);
destinationBuffer += packFloatScalarToSignedTwoByteFixed(destinationBuffer, _scale, 0);
return destinationBuffer - startPosition;
}
@ -91,7 +90,6 @@ int Referential::unpack(const unsigned char* sourceBuffer) {
sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, _translation, 0);
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _rotation);
sourceBuffer += unpackFloatScalarFromSignedTwoByteFixed((const int16_t*) sourceBuffer, &_scale, 0);
return sourceBuffer - startPosition;
}

View file

@ -39,7 +39,6 @@ public:
glm::vec3 getTranslation() const { return _translation; }
glm::quat getRotation() const { return _rotation; }
float getScale() const {return _scale; }
QByteArray getExtraData() const { return _extraDataBuffer; }
virtual void update() {}
@ -62,14 +61,13 @@ protected:
AvatarData* _avatar;
QByteArray _extraDataBuffer;
glm::vec3 _refPosition;
glm::quat _refRotation;
float _refScale;
glm::vec3 _refPosition; // position of object in world-frame
glm::quat _refRotation; // rotation of object in world-frame
glm::vec3 _lastRefDimension; // dimension of object when _translation was last computed
glm::vec3 _translation;
glm::quat _rotation;
float _scale;
glm::vec3 _translation; // offset of avatar in object local-frame
glm::quat _rotation; // rotation of avatar in object local-frame
};
#endif // hifi_Referential_h
#endif // hifi_Referential_h

View file

@ -32,6 +32,7 @@
#include "RenderableModelEntityItem.h"
#include "RenderableSphereEntityItem.h"
#include "RenderableTextEntityItem.h"
#include "RenderableParticleEffectEntityItem.h"
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
@ -53,6 +54,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory)
_currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
_currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
@ -96,7 +98,7 @@ void EntityTreeRenderer::init() {
// make sure our "last avatar position" is something other than our current position, so that on our
// first chance, we'll check for enter/leave entity events.
_lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3(1.0f, 1.0f, 1.0f);
_lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE);
connect(entityTree, &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity);
connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::checkAndCallPreload);
@ -276,10 +278,10 @@ void EntityTreeRenderer::update() {
void EntityTreeRenderer::checkEnterLeaveEntities() {
if (_tree && !_shuttingDown) {
_tree->lockForWrite(); // so that our scripts can do edits if they want
glm::vec3 avatarPosition = _viewState->getAvatarPosition() / (float) TREE_SCALE;
glm::vec3 avatarPosition = _viewState->getAvatarPosition();
if (avatarPosition != _lastAvatarPosition) {
float radius = 1.0f / (float) TREE_SCALE; // for now, assume 1 meter radius
float radius = 1.0f; // for now, assume 1 meter radius
QVector<const EntityItem*> foundEntities;
QVector<EntityItemID> entitiesContainingAvatar;
@ -341,7 +343,7 @@ void EntityTreeRenderer::leaveAllEntities() {
// make sure our "last avatar position" is something other than our current position, so that on our
// first chance, we'll check for enter/leave entity events.
_lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3(1.0f, 1.0f, 1.0f);
_lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE);
_tree->unlock();
}
}
@ -416,8 +418,8 @@ const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityI
}
void EntityTreeRenderer::renderElementProxy(EntityTreeElement* entityTreeElement) {
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float) TREE_SCALE;
float elementSize = entityTreeElement->getScale() * (float) TREE_SCALE;
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter();
float elementSize = entityTreeElement->getScale();
glPushMatrix();
glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z);
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(elementSize, glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
@ -478,10 +480,6 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg
AACube minCube = entity->getMinimumAACube();
AABox entityBox = entity->getAABox();
maxCube.scale((float) TREE_SCALE);
minCube.scale((float) TREE_SCALE);
entityBox.scale((float) TREE_SCALE);
glm::vec3 maxCenter = maxCube.calcCenter();
glm::vec3 minCenter = minCube.calcCenter();
glm::vec3 entityBoxCenter = entityBox.calcCenter();
@ -507,9 +505,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg
glPopMatrix();
glm::vec3 position = entity->getPosition() * (float) TREE_SCALE;
glm::vec3 center = entity->getCenter() * (float) TREE_SCALE;
glm::vec3 dimensions = entity->getDimensions() * (float) TREE_SCALE;
glm::vec3 position = entity->getPosition();
glm::vec3 center = entity->getCenter();
glm::vec3 dimensions = entity->getDimensions();
glm::quat rotation = entity->getRotation();
glPushMatrix();
@ -549,8 +547,6 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
if (entityItem->isVisible()) {
// render entityItem
AABox entityBox = entityItem->getAABox();
entityBox.scale(TREE_SCALE);
// TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum...

View file

@ -25,9 +25,9 @@ EntityItem* RenderableBoxEntityItem::factory(const EntityItemID& entityID, const
void RenderableBoxEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
assert(getType() == EntityTypes::Box);
glm::vec3 position = getPositionInMeters();
glm::vec3 center = getCenter() * (float)TREE_SCALE;
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
glm::vec3 position = getPosition();
glm::vec3 center = getCenter();
glm::vec3 dimensions = getDimensions();
glm::quat rotation = getRotation();
const float MAX_COLOR = 255.0f;

View file

@ -26,8 +26,8 @@ EntityItem* RenderableLightEntityItem::factory(const EntityItemID& entityID, con
void RenderableLightEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableLightEntityItem::render");
assert(getType() == EntityTypes::Light);
glm::vec3 position = getPositionInMeters();
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
glm::vec3 position = getPosition();
glm::vec3 dimensions = getDimensions();
glm::quat rotation = getRotation();
float largestDiameter = glm::max(dimensions.x, dimensions.y, dimensions.z);

View file

@ -114,9 +114,9 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
bool drawAsModel = hasModel();
glm::vec3 position = getPosition() * (float)TREE_SCALE;
float size = getSize() * (float)TREE_SCALE;
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
glm::vec3 position = getPosition();
glm::vec3 dimensions = getDimensions();
float size = glm::length(dimensions);
if (drawAsModel) {
remapTextures();
@ -260,23 +260,10 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
if (!_model) {
return true;
}
glm::vec3 originInMeters = origin * (float)TREE_SCALE;
QString extraInfo;
float localDistance;
//qDebug() << "RenderableModelEntityItem::findDetailedRayIntersection() precisionPicking:" << precisionPicking;
bool intersectsModel = _model->findRayIntersectionAgainstSubMeshes(originInMeters, direction,
localDistance, face, extraInfo, precisionPicking);
if (intersectsModel) {
// NOTE: findRayIntersectionAgainstSubMeshes() does work in meters, but we're expected to return
// results in tree scale.
distance = localDistance / (float)TREE_SCALE;
}
return intersectsModel; // we only got here if we intersected our non-aabox
QString extraInfo;
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking);
}

View file

@ -0,0 +1,90 @@
//
// RenderableParticleEffectEntityItem.cpp
// interface/src
//
// Created by Jason Rickwald on 3/2/15.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <glm/gtx/quaternion.hpp>
#include <gpu/GPUConfig.h>
#include <DependencyManager.h>
#include <DeferredLightingEffect.h>
#include <PerfStat.h>
#include <GeometryCache.h>
#include "RenderableParticleEffectEntityItem.h"
EntityItem* RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new RenderableParticleEffectEntityItem(entityID, properties);
}
RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
ParticleEffectEntityItem(entityItemID, properties) {
_cacheID = DependencyManager::get<GeometryCache>()->allocateID();
}
void RenderableParticleEffectEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render");
assert(getType() == EntityTypes::ParticleEffect);
float pa_rad = getParticleRadius();
const float MAX_COLOR = 255.0f;
glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR,
getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha());
// Right now we're just iterating over particles and rendering as a cross of four quads.
// This is pretty dumb, it was quick enough to code up. Really, there should be many
// rendering modes, including the all-important textured billboards.
QVector<glm::vec3>* pointVec = new QVector<glm::vec3>(_paCount * VERTS_PER_PARTICLE);
quint32 paIter = _paHead;
while (_paLife[paIter] > 0.0f) {
int j = paIter * XYZ_STRIDE;
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad));
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad));
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad));
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad));
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad));
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad));
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad));
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad));
paIter = (paIter + 1) % _maxParticles;
}
DependencyManager::get<GeometryCache>()->updateVertices(_cacheID, *pointVec, paColor);
glPushMatrix();
glm::vec3 position = getPosition();
glTranslatef(position.x, position.y, position.z);
glm::quat rotation = getRotation();
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glPushMatrix();
glm::vec3 positionToCenter = getCenter() - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
DependencyManager::get<GeometryCache>()->renderVertices(gpu::QUADS, _cacheID);
glPopMatrix();
glPopMatrix();
delete pointVec;
};

View file

@ -0,0 +1,28 @@
//
// RenderableParticleEffectEntityItem.h
// interface/src/entities
//
// Created by Jason Rickwald on 3/2/15.
//
// 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_RenderableParticleEffectEntityItem_h
#define hifi_RenderableParticleEffectEntityItem_h
#include <ParticleEffectEntityItem.h>
class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
virtual void render(RenderArgs* args);
protected:
int _cacheID;
const int VERTS_PER_PARTICLE = 16;
};
#endif // hifi_RenderableParticleEffectEntityItem_h

View file

@ -26,9 +26,9 @@ EntityItem* RenderableSphereEntityItem::factory(const EntityItemID& entityID, co
void RenderableSphereEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableSphereEntityItem::render");
assert(getType() == EntityTypes::Sphere);
glm::vec3 position = getPositionInMeters();
glm::vec3 center = getCenterInMeters();
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
glm::vec3 position = getPosition();
glm::vec3 center = getCenter();
glm::vec3 dimensions = getDimensions();
glm::quat rotation = getRotation();
const float MAX_COLOR = 255.0f;

View file

@ -30,8 +30,8 @@ EntityItem* RenderableTextEntityItem::factory(const EntityItemID& entityID, cons
void RenderableTextEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableTextEntityItem::render");
assert(getType() == EntityTypes::Text);
glm::vec3 position = getPositionInMeters();
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
glm::vec3 position = getPosition();
glm::vec3 dimensions = getDimensions();
glm::vec3 halfDimensions = dimensions / 2.0f;
glm::quat rotation = getRotation();
float leftMargin = 0.1f;

View file

@ -25,7 +25,7 @@ AddEntityOperator::AddEntityOperator(EntityTree* tree,
{
// caller must have verified existence of newEntity
assert(_newEntity);
_newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, 1.0f);
_newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, (float)TREE_SCALE);
}
bool AddEntityOperator::preRecursion(OctreeElement* element) {

View file

@ -60,7 +60,7 @@ bool DeleteEntityOperator::subTreeContainsSomeEntitiesToDelete(OctreeElement* el
// If we don't have an old entity, then we don't contain the entity, otherwise
// check the bounds
if (_entitiesToDelete.size() > 0) {
AACube elementCube = element->getAACube();
const AACube& elementCube = element->getAACube();
foreach(const EntityToDeleteDetails& details, _entitiesToDelete) {
if (elementCube.contains(details.cube)) {
containsEntity = true;

View file

@ -501,8 +501,12 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
EntityPropertyFlags propertyFlags = encodedPropertyFlags;
dataAt += propertyFlags.getEncodedLength();
bytesRead += propertyFlags.getEncodedLength();
READ_ENTITY_PROPERTY_SETTER(PROP_POSITION, glm::vec3, updatePosition);
bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS);
if (useMeters) {
READ_ENTITY_PROPERTY_SETTER(PROP_POSITION, glm::vec3, updatePosition);
} else {
READ_ENTITY_PROPERTY_SETTER(PROP_POSITION, glm::vec3, updatePositionInDomainUnits);
}
// Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) {
@ -516,18 +520,31 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
}
}
} else {
READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensions);
if (useMeters) {
READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensions);
} else {
READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensionsInDomainUnits);
}
}
READ_ENTITY_PROPERTY_QUAT_SETTER(PROP_ROTATION, updateRotation);
READ_ENTITY_PROPERTY_SETTER(PROP_DENSITY, float, updateDensity);
READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity);
READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity);
if (useMeters) {
READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity);
READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity);
} else {
READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits);
READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits);
}
READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping);
READ_ENTITY_PROPERTY_SETTER(PROP_LIFETIME, float, updateLifetime);
READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT, setScript);
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint);
READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
if (useMeters) {
READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
} else {
READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees);
}
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping);
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible);
READ_ENTITY_PROPERTY_SETTER(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions);
@ -587,8 +604,7 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s
}
float EntityItem::computeMass() const {
// NOTE: we group the operations here in and attempt to reduce floating point error.
return ((_density * (_volumeMultiplier * _dimensions.x)) * _dimensions.y) * _dimensions.z;
return _density * _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z;
}
void EntityItem::setDensity(float density) {
@ -609,10 +625,7 @@ void EntityItem::setMass(float mass) {
// we must protect the density range to help maintain stability of physics simulation
// therefore this method might not accept the mass that is supplied.
// NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due
// to the TREE_SCALE transformation) with the first dimension component (typically a very small number)
// in an attempt to reduce floating point error of the final result.
float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z;
float volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z;
// compute new density
const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3
@ -674,41 +687,36 @@ void EntityItem::simulate(const quint64& now) {
void EntityItem::simulateKinematicMotion(float timeElapsed) {
if (hasAngularVelocity()) {
// angular damping
glm::vec3 angularVelocity = getAngularVelocity();
if (_angularDamping > 0.0f) {
angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
_angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
#ifdef WANT_DEBUG
qDebug() << " angularDamping :" << _angularDamping;
qDebug() << " newAngularVelocity:" << angularVelocity;
qDebug() << " newAngularVelocity:" << _angularVelocity;
#endif
setAngularVelocity(angularVelocity);
}
float angularSpeed = glm::length(_angularVelocity);
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; //
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.0017453f; // 0.0017453 rad/sec = 0.1f degrees/sec
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
if (angularSpeed > 0.0f) {
_dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
}
setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
_angularVelocity = ENTITY_ITEM_ZERO_VEC3;
} else {
// NOTE: angularSpeed is currently in degrees/sec!!!
// TODO: Andrew to convert to radians/sec
glm::vec3 angularVelocity = glm::radians(_angularVelocity);
// 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) {
glm::quat dQ = computeBulletRotationStep(angularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP);
glm::quat dQ = computeBulletRotationStep(_angularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP);
rotation = glm::normalize(dQ * rotation);
dt -= PHYSICS_ENGINE_FIXED_SUBSTEP;
}
// NOTE: this final partial substep can drift away from a real Bullet simulation however
// it only becomes significant for rapidly rotating objects
// (e.g. around PI/4 radians per substep, or 7.5 rotations/sec at 60 substeps/sec).
glm::quat dQ = computeBulletRotationStep(angularVelocity, dt);
glm::quat dQ = computeBulletRotationStep(_angularVelocity, dt);
rotation = glm::normalize(dQ * rotation);
setRotation(rotation);
@ -753,7 +761,7 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) {
}
float speed = glm::length(velocity);
const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE; // 1mm/sec
const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f; // 1mm/sec
if (speed < EPSILON_LINEAR_VELOCITY_LENGTH) {
setVelocity(ENTITY_ITEM_ZERO_VEC3);
if (speed > 0.0f) {
@ -793,12 +801,12 @@ EntityItemProperties EntityItem::getProperties() const {
properties._type = getType();
COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPositionInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensionsInMeters); // NOTE: radius is obsolete
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);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocityInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravityInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript);
@ -821,12 +829,12 @@ EntityItemProperties EntityItem::getProperties() const {
bool EntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false;
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePositionInMeters); // this will call recalculate collision shape if needed
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, updateDimensionsInMeters); // NOTE: radius is obsolete
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePosition); // this will call recalculate collision shape if needed
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, updateDimensions); // NOTE: radius is obsolete
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, updateDensity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocityInMeters);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravityInMeters);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
@ -888,11 +896,6 @@ void EntityItem::recordCreationTime() {
}
// TODO: is this really correct? how do we use size, does it need to handle rotation?
float EntityItem::getSize() const {
return glm::length(_dimensions);
}
// TODO: doesn't this need to handle rotation?
glm::vec3 EntityItem::getCenter() const {
return _position + (_dimensions * (glm::vec3(0.5f,0.5f,0.5f) - _registrationPoint));
@ -951,8 +954,7 @@ AACube EntityItem::getMinimumAACube() const {
return AACube(cornerOfCube, longestSide);
}
AABox EntityItem::getAABox() const {
AABox EntityItem::getAABox() const {
// _position represents the position of the registration point.
glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint;
@ -967,6 +969,11 @@ AABox EntityItem::getAABox() const {
return AABox(rotatedExtentsRelativeToRegistrationPoint);
}
AABox EntityItem::getAABoxInDomainUnits() const {
AABox box = getAABox();
box.scale(1.0f / (float)TREE_SCALE);
return box;
}
// NOTE: This should only be used in cases of old bitstreams which only contain radius data
// 0,0,0 --> maxDimension,maxDimension,maxDimension
@ -992,48 +999,41 @@ void EntityItem::setRadius(float value) {
// ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2)
// ... radius = sqrt(3 x maxDimension ^ 2) / 2.0f;
float EntityItem::getRadius() const {
float length = glm::length(_dimensions);
float radius = length / 2.0f;
return radius;
return 0.5f * glm::length(_dimensions);
}
void EntityItem::computeShapeInfo(ShapeInfo& info) const {
info.setParams(getShapeType(), 0.5f * getDimensionsInMeters());
info.setParams(getShapeType(), 0.5f * getDimensions());
}
const float MIN_POSITION_DELTA = 0.0001f;
const float MIN_DIMENSIONS_DELTA = 0.0005f;
const float MIN_ALIGNMENT_DOT = 0.999999f;
const float MIN_VELOCITY_DELTA = 0.01f;
const float MIN_DAMPING_DELTA = 0.001f;
const float MIN_GRAVITY_DELTA = 0.001f;
const float MIN_SPIN_DELTA = 0.0003f;
void EntityItem::updatePositionInDomainUnits(const glm::vec3& value) {
glm::vec3 position = value * (float)TREE_SCALE;
updatePosition(position);
}
void EntityItem::updatePosition(const glm::vec3& value) {
if (glm::distance(_position, value) * (float)TREE_SCALE > MIN_POSITION_DELTA) {
_position = value;
if (glm::distance(_position, value) > MIN_POSITION_DELTA) {
_position = value;
_dirtyFlags |= EntityItem::DIRTY_POSITION;
}
}
void EntityItem::updatePositionInMeters(const glm::vec3& value) {
glm::vec3 position = glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f);
if (glm::distance(_position, position) * (float)TREE_SCALE > MIN_POSITION_DELTA) {
_position = position;
_dirtyFlags |= EntityItem::DIRTY_POSITION;
}
void EntityItem::updateDimensionsInDomainUnits(const glm::vec3& value) {
glm::vec3 dimensions = value * (float)TREE_SCALE;
updateDimensions(dimensions);
}
void EntityItem::updateDimensions(const glm::vec3& value) {
if (_dimensions != value) {
_dimensions = glm::abs(value);
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
}
}
void EntityItem::updateDimensionsInMeters(const glm::vec3& value) {
glm::vec3 dimensions = glm::abs(value) / (float) TREE_SCALE;
if (_dimensions != dimensions) {
_dimensions = dimensions;
if (glm::distance(_dimensions, value) > MIN_DIMENSIONS_DELTA) {
_dimensions = value;
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
}
}
@ -1050,10 +1050,7 @@ void EntityItem::updateMass(float mass) {
// we must protect the density range to help maintain stability of physics simulation
// therefore this method might not accept the mass that is supplied.
// NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due
// to the TREE_SCALE transformation) with the first dimension component (typically a very small number)
// in an attempt to reduce floating point error of the final result.
float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z;
float volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z;
// compute new density
float newDensity = _density;
@ -1072,24 +1069,17 @@ void EntityItem::updateMass(float mass) {
}
}
void EntityItem::updateVelocity(const glm::vec3& value) {
if (glm::distance(_velocity, value) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) {
if (glm::length(value) * (float)TREE_SCALE < MIN_VELOCITY_DELTA) {
_velocity = ENTITY_ITEM_ZERO_VEC3;
} else {
_velocity = value;
}
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
void EntityItem::updateVelocityInDomainUnits(const glm::vec3& value) {
glm::vec3 velocity = value * (float)TREE_SCALE;
updateVelocity(velocity);
}
void EntityItem::updateVelocityInMeters(const glm::vec3& value) {
glm::vec3 velocity = value / (float) TREE_SCALE;
if (glm::distance(_velocity, velocity) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) {
void EntityItem::updateVelocity(const glm::vec3& value) {
if (glm::distance(_velocity, value) > MIN_VELOCITY_DELTA) {
if (glm::length(value) < MIN_VELOCITY_DELTA) {
_velocity = ENTITY_ITEM_ZERO_VEC3;
} else {
_velocity = velocity;
_velocity = value;
}
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
@ -1102,17 +1092,14 @@ void EntityItem::updateDamping(float value) {
}
}
void EntityItem::updateGravity(const glm::vec3& value) {
if (glm::distance(_gravity, value) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) {
_gravity = value;
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
void EntityItem::updateGravityInDomainUnits(const glm::vec3& value) {
glm::vec3 gravity = value * (float) TREE_SCALE;
updateGravity(gravity);
}
void EntityItem::updateGravityInMeters(const glm::vec3& value) {
glm::vec3 gravity = value / (float) TREE_SCALE;
if ( glm::distance(_gravity, gravity) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) {
_gravity = gravity;
void EntityItem::updateGravity(const glm::vec3& value) {
if ( glm::distance(_gravity, value) > MIN_GRAVITY_DELTA) {
_gravity = value;
_dirtyFlags |= EntityItem::DIRTY_VELOCITY;
}
}

View file

@ -37,7 +37,7 @@ class EntityTreeElementExtraEncodeData;
#define debugTime(T, N) qPrintable(QString("%1 [ %2 ago]").arg(T, 16, 10).arg(formatUsecTime(N - T), 15))
#define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10))
#define debugTreeVector(V) V << "[" << (V * (float)TREE_SCALE) << " in meters ]"
#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
@ -145,26 +145,27 @@ public:
// attributes applicable to all entity types
EntityTypes::EntityType getType() const { return _type; }
const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0)
glm::vec3 getPositionInMeters() const { return _position * (float) TREE_SCALE; } /// get position in meters
glm::vec3 getPositionInDomainUnits() const { return _position / (float)TREE_SCALE; } /// get position in domain scale units (0.0 - 1.0)
const glm::vec3& getPosition() const { return _position; } /// get position in meters
/// set position in domain scale units (0.0 - 1.0)
void setPosition(const glm::vec3& value) { _position = value; }
void setPositionInMeters(const glm::vec3& value) /// set position in meter units (0.0 - TREE_SCALE)
{ setPosition(glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f)); }
void setPositionInDomainUnits(const glm::vec3& value)
{ setPosition(glm::clamp(value, 0.0f, 1.0f) * (float)TREE_SCALE); }
void setPosition(const glm::vec3& value) {
_position = value;
}
glm::vec3 getCenter() const; /// calculates center of the entity in domain scale units (0.0 - 1.0)
glm::vec3 getCenterInMeters() const { return getCenter() * (float) TREE_SCALE; }
glm::vec3 getCenterInDomainUnits() const { return getCenter() / (float) TREE_SCALE; }
glm::vec3 getCenter() const;
const glm::vec3& getDimensions() const { return _dimensions; } /// get dimensions in domain scale units (0.0 - 1.0)
glm::vec3 getDimensionsInMeters() const { return _dimensions * (float) TREE_SCALE; } /// get dimensions in meters
float getLargestDimension() const { return glm::length(_dimensions); } /// get the largest possible dimension
glm::vec3 getDimensionsInDomainUnits() const { return _dimensions / (float)TREE_SCALE; } /// get dimensions in domain scale units (0.0 - 1.0)
const glm::vec3& getDimensions() const { return _dimensions; } /// get dimensions in meters
/// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately
virtual void setDimensions(const glm::vec3& value) { _dimensions = value; }
/// set dimensions in domain scale units (0.0 - 1.0)
virtual void setDimensionsInDomainUnits(const glm::vec3& value) { _dimensions = glm::abs(value) * (float)TREE_SCALE; }
/// set dimensions in meter units (0.0 - TREE_SCALE) this will also reset radius appropriately
void setDimensionsInMeters(const glm::vec3& value) { setDimensions(value / (float) TREE_SCALE); }
/// set dimensions in meter units (0.0 - TREE_SCALE)
virtual void setDimensions(const glm::vec3& value) { _dimensions = glm::abs(value); }
const glm::quat& getRotation() const { return _rotation; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
@ -181,16 +182,16 @@ public:
float getDensity() const { return _density; }
const glm::vec3& getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second
glm::vec3 getVelocityInMeters() const { return _velocity * (float) TREE_SCALE; } /// get velocity in meters
void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in domain scale units (0.0-1.0) per second
void setVelocityInMeters(const glm::vec3& value) { _velocity = value / (float) TREE_SCALE; } /// velocity in meters
glm::vec3 getVelocityInDomainUnits() const { return _velocity / (float)TREE_SCALE; } /// velocity in domain scale units (0.0-1.0) per second
const glm::vec3 getVelocity() const { return _velocity; } /// get velocity in meters
void setVelocityInDomainUnits(const glm::vec3& value) { _velocity = value * (float)TREE_SCALE; } /// velocity in domain scale units (0.0-1.0) per second
void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in meters
bool hasVelocity() const { return _velocity != ENTITY_ITEM_ZERO_VEC3; }
const glm::vec3& getGravity() const { return _gravity; } /// gravity in domain scale units (0.0-1.0) per second squared
glm::vec3 getGravityInMeters() const { return _gravity * (float) TREE_SCALE; } /// get gravity in meters
void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in domain scale units (0.0-1.0) per second squared
void setGravityInMeters(const glm::vec3& value) { _gravity = value / (float) TREE_SCALE; } /// gravity in meters
glm::vec3 getGravityInDomainUnits() const { return _gravity / (float)TREE_SCALE; } /// gravity in domain scale units (0.0-1.0) per second squared
const glm::vec3& getGravity() const { return _gravity; } /// get gravity in meters
void setGravityInDomainUnits(const glm::vec3& value) { _gravity = value * (float)TREE_SCALE; } /// gravity in domain scale units (0.0-1.0) per second squared
void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in meters
bool hasGravity() const { return _gravity != ENTITY_ITEM_ZERO_VEC3; }
float getDamping() const { return _damping; }
@ -212,10 +213,10 @@ public:
quint64 getExpiry() const;
// position, size, and bounds related helpers
float getSize() const; /// get maximum dimension in domain scale units (0.0 - 1.0)
AACube getMaximumAACube() const;
AACube getMinimumAACube() const;
AABox getAABox() const; /// axis aligned bounding box in domain scale units (0.0 - 1.0)
AABox getAABox() const; /// axis aligned bounding box in world-frame (meters)
AABox getAABoxInDomainUnits() const; /// axis aligned bounding box in domain scale units (0.0 - 1.0)
const QString& getScript() const { return _script; }
void setScript(const QString& value) { _script = value; }
@ -250,29 +251,31 @@ public:
const QString& getUserData() const { return _userData; }
void setUserData(const QString& value) { _userData = value; }
// TODO: We need to get rid of these users of getRadius()...
// TODO: get rid of users of getRadius()...
float getRadius() const;
virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); }
virtual bool containsInDomainUnits(const glm::vec3& point) const { return getAABoxInDomainUnits().contains(point); }
virtual void computeShapeInfo(ShapeInfo& info) const;
/// return preferred shape type (actual physical shape may differ)
virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; }
// updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags
void updatePositionInDomainUnits(const glm::vec3& value);
void updatePosition(const glm::vec3& value);
void updatePositionInMeters(const glm::vec3& value);
void updateDimensionsInDomainUnits(const glm::vec3& value);
void updateDimensions(const glm::vec3& value);
void updateDimensionsInMeters(const glm::vec3& value);
void updateRotation(const glm::quat& rotation);
void updateDensity(float value);
void updateMass(float value);
void updateVelocityInDomainUnits(const glm::vec3& value);
void updateVelocity(const glm::vec3& value);
void updateVelocityInMeters(const glm::vec3& value);
void updateDamping(float value);
void updateGravityInDomainUnits(const glm::vec3& value);
void updateGravity(const glm::vec3& value);
void updateGravityInMeters(const glm::vec3& value);
void updateAngularVelocity(const glm::vec3& value);
void updateAngularVelocityInDegrees(const glm::vec3& value) { updateAngularVelocity(glm::radians(value)); }
void updateAngularDamping(float value);
void updateIgnoreForCollisions(bool value);
void updateCollisionsWillMove(bool value);
@ -318,11 +321,10 @@ protected:
float _glowLevel;
float _localRenderAlpha;
float _density = ENTITY_ITEM_DEFAULT_DENSITY; // kg/m^3
// NOTE: _volumeMultiplier is used to compute volume:
// volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z = m^3
// DANGER: due to the size of TREE_SCALE the _volumeMultiplier is always a large number, and therefore
// will tend to introduce floating point error. We must keep this in mind when using it.
float _volumeMultiplier = (float)TREE_SCALE * (float)TREE_SCALE * (float)TREE_SCALE;
// NOTE: _volumeMultiplier is used to allow some mass properties code exist in the EntityItem base class
// rather than in all of the derived classes. If we ever collapse these classes to one we could do it a
// different way.
float _volumeMultiplier = 1.0f;
glm::vec3 _velocity;
glm::vec3 _gravity;
float _damping;

View file

@ -23,6 +23,7 @@
#include "EntityItemPropertiesDefaults.h"
#include "ModelEntityItem.h"
#include "TextEntityItem.h"
#include "ParticleEffectEntityItem.h"
EntityItemProperties::EntityItemProperties() :
@ -39,6 +40,7 @@ EntityItemProperties::EntityItemProperties() :
CONSTRUCT_PROPERTY(script, ENTITY_ITEM_DEFAULT_SCRIPT),
CONSTRUCT_PROPERTY(color, ),
CONSTRUCT_PROPERTY(modelURL, ""),
CONSTRUCT_PROPERTY(collisionModelURL, ""),
CONSTRUCT_PROPERTY(animationURL, ""),
CONSTRUCT_PROPERTY(animationFPS, ModelEntityItem::DEFAULT_ANIMATION_FPS),
CONSTRUCT_PROPERTY(animationFrameIndex, ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX),
@ -61,6 +63,13 @@ EntityItemProperties::EntityItemProperties() :
CONSTRUCT_PROPERTY(textColor, TextEntityItem::DEFAULT_TEXT_COLOR),
CONSTRUCT_PROPERTY(backgroundColor, TextEntityItem::DEFAULT_BACKGROUND_COLOR),
CONSTRUCT_PROPERTY(shapeType, SHAPE_TYPE_NONE),
CONSTRUCT_PROPERTY(maxParticles, ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES),
CONSTRUCT_PROPERTY(lifespan, ParticleEffectEntityItem::DEFAULT_LIFESPAN),
CONSTRUCT_PROPERTY(emitRate, ParticleEffectEntityItem::DEFAULT_EMIT_RATE),
CONSTRUCT_PROPERTY(emitDirection, ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION),
CONSTRUCT_PROPERTY(emitStrength, ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH),
CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY),
CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS),
_id(UNKNOWN_ENTITY_ID),
_idSet(false),
@ -150,6 +159,7 @@ void EntityItemProperties::debugDump() const {
qDebug() << " _position=" << _position.x << "," << _position.y << "," << _position.z;
qDebug() << " _dimensions=" << getDimensions();
qDebug() << " _modelURL=" << _modelURL;
qDebug() << " _collisionModelURL=" << _collisionModelURL;
qDebug() << " changed properties...";
EntityPropertyFlags props = getChangedProperties();
props.debugDumpBits();
@ -205,6 +215,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script);
CHECK_PROPERTY_CHANGE(PROP_COLOR, color);
CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL);
CHECK_PROPERTY_CHANGE(PROP_COLLISION_MODEL_URL, collisionModelURL);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_URL, animationURL);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_PLAYING, animationIsPlaying);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FRAME_INDEX, animationFrameIndex);
@ -228,6 +239,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor);
CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_COLOR, backgroundColor);
CHECK_PROPERTY_CHANGE(PROP_SHAPE_TYPE, shapeType);
CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles);
CHECK_PROPERTY_CHANGE(PROP_LIFESPAN, lifespan);
CHECK_PROPERTY_CHANGE(PROP_EMIT_RATE, emitRate);
CHECK_PROPERTY_CHANGE(PROP_EMIT_DIRECTION, emitDirection);
CHECK_PROPERTY_CHANGE(PROP_EMIT_STRENGTH, emitStrength);
CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity);
CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius);
return changedProperties;
}
@ -261,6 +279,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE(visible);
COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR(color);
COPY_PROPERTY_TO_QSCRIPTVALUE(modelURL);
COPY_PROPERTY_TO_QSCRIPTVALUE(collisionModelURL);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationURL);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationIsPlaying);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS);
@ -282,6 +301,13 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(textColor, getTextColor());
COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(backgroundColor, getBackgroundColor());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(shapeType, getShapeTypeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE(maxParticles);
COPY_PROPERTY_TO_QSCRIPTVALUE(lifespan);
COPY_PROPERTY_TO_QSCRIPTVALUE(emitRate);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(emitDirection);
COPY_PROPERTY_TO_QSCRIPTVALUE(emitStrength);
COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity);
COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius);
// Sitting properties support
QScriptValue sittingPoints = engine->newObject();
@ -295,7 +321,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
sittingPoints.setProperty("length", _sittingPoints.size());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable
AABox aaBox = getAABoxInMeters();
AABox aaBox = getAABox();
QScriptValue boundingBox = engine->newObject();
QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner());
QScriptValue topFarLeft = vec3toScriptValue(engine, aaBox.calcTopFarLeft());
@ -334,6 +360,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(visible, setVisible);
COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(color, setColor);
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(modelURL, setModelURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(collisionModelURL, setCollisionModelURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(animationURL, setAnimationURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(animationIsPlaying, setAnimationIsPlaying);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS);
@ -355,6 +382,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(textColor, setTextColor);
COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(backgroundColor, setBackgroundColor);
COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(shapeType, ShapeType);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(maxParticles, setMaxParticles);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(lifespan, setLifespan);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitRate, setEmitRate);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(emitDirection, setEmitDirection);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitStrength, setEmitStrength);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localGravity, setLocalGravity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(particleRadius, setParticleRadius);
_lastEdited = usecTimestampNow();
}
@ -512,6 +546,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
if (properties.getType() == EntityTypes::Model) {
APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, appendValue, properties.getModelURL());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_MODEL_URL, appendValue, properties.getCollisionModelURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, appendValue, properties.getAnimationURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, properties.getAnimationFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, properties.getAnimationFrameIndex());
@ -528,6 +563,16 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, appendValue, properties.getExponent());
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, appendValue, properties.getCutoff());
}
if (properties.getType() == EntityTypes::ParticleEffect) {
APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, appendValue, properties.getMaxParticles());
APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, appendValue, properties.getLifespan());
APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, appendValue, properties.getEmitRate());
APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, appendValue, properties.getEmitDirection());
APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, properties.getEmitStrength());
APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, properties.getLocalGravity());
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, properties.getParticleRadius());
}
}
if (propertyCount > 0) {
int endOfEntityItemData = packetData->getUncompressedByteOffset();
@ -730,6 +775,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
if (properties.getType() == EntityTypes::Model) {
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MODEL_URL, setModelURL);
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_COLLISION_MODEL_URL, setCollisionModelURL);
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ANIMATION_URL, setAnimationURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FPS, float, setAnimationFPS);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FRAME_INDEX, float, setAnimationFrameIndex);
@ -746,6 +792,16 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff);
}
if (properties.getType() == EntityTypes::ParticleEffect) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, float, setMaxParticles);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFESPAN, float, setLifespan);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RATE, float, setEmitRate);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_STRENGTH, float, setEmitStrength);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCAL_GRAVITY, float, setLocalGravity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius);
}
return valid;
}
@ -796,6 +852,7 @@ void EntityItemProperties::markAllChanged() {
_visibleChanged = true;
_colorChanged = true;
_modelURLChanged = true;
_collisionModelURLChanged = true;
_animationURLChanged = true;
_animationIsPlayingChanged = true;
_animationFrameIndexChanged = true;
@ -818,18 +875,20 @@ void EntityItemProperties::markAllChanged() {
_textColorChanged = true;
_backgroundColorChanged = true;
_shapeTypeChanged = true;
}
AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const {
AACube maxCube = getMaximumAACubeInMeters();
maxCube.scale(1.0f / (float)TREE_SCALE);
return maxCube;
_maxParticlesChanged = true;
_lifespanChanged = true;
_emitRateChanged = true;
_emitDirectionChanged = true;
_emitStrengthChanged = true;
_localGravityChanged = true;
_particleRadiusChanged = true;
}
/// The maximum bounding cube for the entity, independent of it's rotation.
/// This accounts for the registration point (upon which rotation occurs around).
///
AACube EntityItemProperties::getMaximumAACubeInMeters() const {
AACube EntityItemProperties::getMaximumAACube() const {
// * we know that the position is the center of rotation
glm::vec3 centerOfRotation = _position; // also where _registration point is
@ -853,7 +912,7 @@ AACube EntityItemProperties::getMaximumAACubeInMeters() const {
}
// The minimum bounding box for the entity.
AABox EntityItemProperties::getAABoxInMeters() const {
AABox EntityItemProperties::getAABox() const {
// _position represents the position of the registration point.
glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint;

View file

@ -83,9 +83,18 @@ enum EntityPropertyList {
PROP_ANIMATION_SETTINGS,
PROP_USER_DATA,
PROP_SHAPE_TYPE,
// used by ParticleEffect entities
PROP_MAX_PARTICLES,
PROP_LIFESPAN,
PROP_EMIT_RATE,
PROP_EMIT_DIRECTION,
PROP_EMIT_STRENGTH,
PROP_LOCAL_GRAVITY,
PROP_PARTICLE_RADIUS,
// NOTE: add new properties ABOVE this line and then modify PROP_LAST_ITEM below
PROP_LAST_ITEM = PROP_SHAPE_TYPE,
PROP_LAST_ITEM = PROP_PARTICLE_RADIUS,
// These properties of TextEntity piggy back off of properties of ModelEntities, the type doesn't matter
// since the derived class knows how to interpret it's own properties and knows the types it expects
@ -93,6 +102,7 @@ enum EntityPropertyList {
PROP_TEXT = PROP_MODEL_URL,
PROP_LINE_HEIGHT = PROP_ANIMATION_URL,
PROP_BACKGROUND_COLOR = PROP_ANIMATION_FPS,
PROP_COLLISION_MODEL_URL,
};
typedef PropertyFlags<EntityPropertyList> EntityPropertyFlags;
@ -110,6 +120,7 @@ class EntityItemProperties {
friend class SphereEntityItem; // TODO: consider removing this friend relationship and use public methods
friend class LightEntityItem; // TODO: consider removing this friend relationship and use public methods
friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods
friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods
public:
EntityItemProperties();
virtual ~EntityItemProperties();
@ -129,9 +140,8 @@ public:
/// used by EntityScriptingInterface to return EntityItemProperties for unknown models
void setIsUnknownID() { _id = UNKNOWN_ENTITY_ID; _idSet = true; }
AACube getMaximumAACubeInTreeUnits() const;
AACube getMaximumAACubeInMeters() const;
AABox getAABoxInMeters() const;
AACube getMaximumAACube() const;
AABox getAABox() const;
void debugDump() const;
void setLastEdited(quint64 usecTime);
@ -155,6 +165,7 @@ public:
DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString);
DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor);
DEFINE_PROPERTY_REF(PROP_MODEL_URL, ModelURL, modelURL, QString);
DEFINE_PROPERTY_REF(PROP_COLLISION_MODEL_URL, CollisionModelURL, collisionModelURL, QString);
DEFINE_PROPERTY_REF(PROP_ANIMATION_URL, AnimationURL, animationURL, QString);
DEFINE_PROPERTY(PROP_ANIMATION_FPS, AnimationFPS, animationFPS, float);
DEFINE_PROPERTY(PROP_ANIMATION_FRAME_INDEX, AnimationFrameIndex, animationFrameIndex, float);
@ -177,6 +188,13 @@ public:
DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, xColor);
DEFINE_PROPERTY_REF(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, xColor);
DEFINE_PROPERTY_REF_ENUM(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType);
DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32);
DEFINE_PROPERTY(PROP_LIFESPAN, Lifespan, lifespan, float);
DEFINE_PROPERTY(PROP_EMIT_RATE, EmitRate, emitRate, float);
DEFINE_PROPERTY_REF(PROP_EMIT_DIRECTION, EmitDirection, emitDirection, glm::vec3);
DEFINE_PROPERTY(PROP_EMIT_STRENGTH, EmitStrength, emitStrength, float);
DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float);
DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float);
public:
float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); }
@ -275,6 +293,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Script, script, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Color, color, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ModelURL, modelURL, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CollisionModelURL, collisionModelURL, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AnimationURL, animationURL, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AnimationFPS, animationFPS, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AnimationFrameIndex, animationFrameIndex, "");
@ -296,6 +315,13 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, TextColor, textColor, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundColor, backgroundColor, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ShapeType, shapeType, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, MaxParticles, maxParticles, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Lifespan, lifespan, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitRate, emitRate, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitDirection, emitDirection, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitStrength, emitStrength, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalGravity, localGravity, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, "");
debug << " last edited:" << properties.getLastEdited() << "\n";
debug << " edited ago:" << properties.getEditedAgo() << "\n";

View file

@ -20,8 +20,6 @@
// creating a new one on the stack so we declare the ZERO_VEC3 constant as an optimization.
const glm::vec3 ENTITY_ITEM_ZERO_VEC3(0.0f);
const glm::vec3 REGULAR_GRAVITY = glm::vec3(0, -9.8f / (float)TREE_SCALE, 0);
const bool ENTITY_ITEM_DEFAULT_LOCKED = false;
const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString("");
@ -37,7 +35,7 @@ const float ENTITY_ITEM_DEFAULT_LIFETIME = ENTITY_ITEM_IMMORTAL_LIFETIME;
const glm::quat ENTITY_ITEM_DEFAULT_ROTATION;
const float ENTITY_ITEM_DEFAULT_WIDTH = 0.1f;
const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH) / (float)TREE_SCALE;
const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH);
const float ENTITY_ITEM_DEFAULT_VOLUME = ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH;
const float ENTITY_ITEM_MAX_DENSITY = 10000.0f; // kg/m^3 density of silver

View file

@ -182,8 +182,7 @@ EntityItemID EntityScriptingInterface::findClosestEntity(const glm::vec3& center
EntityItemID result(UNKNOWN_ENTITY_ID, UNKNOWN_ENTITY_TOKEN, false);
if (_entityTree) {
_entityTree->lockForRead();
const EntityItem* closestEntity = _entityTree->findClosestEntity(center/(float)TREE_SCALE,
radius/(float)TREE_SCALE);
const EntityItem* closestEntity = _entityTree->findClosestEntity(center, radius);
_entityTree->unlock();
if (closestEntity) {
result.id = closestEntity->getID();
@ -207,7 +206,7 @@ QVector<EntityItemID> EntityScriptingInterface::findEntities(const glm::vec3& ce
if (_entityTree) {
_entityTree->lockForRead();
QVector<const EntityItem*> entities;
_entityTree->findEntities(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, entities);
_entityTree->findEntities(center, radius, entities);
_entityTree->unlock();
foreach (const EntityItem* entity, entities) {

View file

@ -87,7 +87,7 @@ void EntitySimulation::sortEntitiesThatMoved() {
// External changes to entity position/shape are expected to be sorted outside of the EntitySimulation.
PerformanceTimer perfTimer("sortingEntities");
MovingEntitiesOperator moveOperator(_entityTree);
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f);
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE);
QSet<EntityItem*>::iterator itemItr = _entitiesToBeSorted.begin();
while (itemItr != _entitiesToBeSorted.end()) {
EntityItem* entity = *itemItr;
@ -150,7 +150,7 @@ void EntitySimulation::entityChanged(EntityItem* entity) {
bool wasRemoved = false;
uint32_t dirtyFlags = entity->getDirtyFlags();
if (dirtyFlags & EntityItem::DIRTY_POSITION) {
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f);
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE);
AACube newCube = entity->getMaximumAACube();
if (!domainBounds.touches(newCube)) {
qDebug() << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";

View file

@ -35,7 +35,7 @@ const int DIRTY_SIMULATION_FLAGS =
class EntitySimulation : public QObject {
Q_OBJECT
public:
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL) { }
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { }
virtual ~EntitySimulation() { setEntityTree(NULL); }
void lock() { _mutex.lock(); }

View file

@ -424,8 +424,7 @@ bool EntityTree::findNearPointOperation(OctreeElement* element, void* extraData)
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
glm::vec3 penetration;
bool sphereIntersection = entityTreeElement->getAACube().findSpherePenetration(args->position,
args->targetRadius, penetration);
bool sphereIntersection = entityTreeElement->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration);
// If this entityTreeElement contains the point, then search it...
if (sphereIntersection) {
@ -475,8 +474,7 @@ public:
bool EntityTree::findInSphereOperation(OctreeElement* element, void* extraData) {
FindAllNearPointArgs* args = static_cast<FindAllNearPointArgs*>(extraData);
glm::vec3 penetration;
bool sphereIntersection = element->getAACube().findSpherePenetration(args->position,
args->targetRadius, penetration);
bool sphereIntersection = element->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration);
// If this element contains the point, then search it...
if (sphereIntersection) {
@ -511,8 +509,7 @@ public:
bool EntityTree::findInCubeOperation(OctreeElement* element, void* extraData) {
FindEntitiesInCubeArgs* args = static_cast<FindEntitiesInCubeArgs*>(extraData);
const AACube& elementCube = element->getAACube();
if (elementCube.touches(args->_cube)) {
if (element->getAACube().touches(args->_cube)) {
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
entityTreeElement->getEntities(args->_cube, args->_foundEntities);
return true;

View file

@ -96,6 +96,8 @@ public:
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false);
void removeEntityFromSimulation(EntityItem* entity);
/// \param position point of query in world-frame (meters)
/// \param targetRadius radius of query (meters)
const EntityItem* findClosestEntity(glm::vec3 position, float targetRadius);
EntityItem* findEntityByID(const QUuid& id);
EntityItem* findEntityByEntityItemID(const EntityItemID& entityID);
@ -104,14 +106,14 @@ public:
/// finds all entities that touch a sphere
/// \param center the center of the sphere
/// \param radius the radius of the sphere
/// \param center the center of the sphere in world-frame (meters)
/// \param radius the radius of the sphere in world-frame (meters)
/// \param foundEntities[out] vector of const EntityItem*
/// \remark Side effect: any initial contents in foundEntities will be lost
void findEntities(const glm::vec3& center, float radius, QVector<const EntityItem*>& foundEntities);
/// finds all entities that touch a cube
/// \param cube the query cube
/// \param cube the query cube in world-frame (meters)
/// \param foundEntities[out] vector of non-const EntityItem*
/// \remark Side effect: any initial contents in entities will be lost
void findEntities(const AACube& cube, QVector<EntityItem*>& foundEntities);

View file

@ -50,7 +50,7 @@ EntityTreeElement* EntityTreeElement::addChildAtIndex(int index) {
void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) const {
qDebug() << "EntityTreeElement::debugExtraEncodeData()... ";
qDebug() << " element:" << getAACube();
qDebug() << " element:" << _cube;
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
@ -159,7 +159,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, Oct
const bool wantDebug = false;
if (wantDebug) {
qDebug() << "EntityTreeElement::elementEncodeComplete() element:" << getAACube();
qDebug() << "EntityTreeElement::elementEncodeComplete() element:" << _cube;
}
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
@ -194,7 +194,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, Oct
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(childElement));
if (wantDebug) {
qDebug() << "checking child: " << childElement->getAACube();
qDebug() << "checking child: " << childElement->_cube;
qDebug() << " childElement->isLeaf():" << childElement->isLeaf();
qDebug() << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted;
qDebug() << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted;
@ -215,7 +215,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, Oct
}
if (wantDebug) {
qDebug() << "for this element: " << getAACube();
qDebug() << "for this element: " << _cube;
qDebug() << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted;
qDebug() << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
}
@ -302,7 +302,6 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// the entity may not be in view and then in view a frame later, let the client side handle it's view
// frustum culling on rendering.
AACube entityCube = entity->getMaximumAACube();
entityCube.scale(TREE_SCALE);
if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) {
includeThisEntity = false; // out of view, don't include it
}
@ -417,11 +416,11 @@ bool EntityTreeElement::bestFitEntityBounds(const EntityItem* entity) const {
}
bool EntityTreeElement::containsBounds(const EntityItemProperties& properties) const {
return containsBounds(properties.getMaximumAACubeInTreeUnits());
return containsBounds(properties.getMaximumAACube());
}
bool EntityTreeElement::bestFitBounds(const EntityItemProperties& properties) const {
return bestFitBounds(properties.getMaximumAACubeInTreeUnits());
return bestFitBounds(properties.getMaximumAACube());
}
bool EntityTreeElement::containsBounds(const AACube& bounds) const {
@ -441,14 +440,14 @@ bool EntityTreeElement::bestFitBounds(const AABox& bounds) const {
}
bool EntityTreeElement::containsBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const {
glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, 1.0f);
glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, 1.0f);
glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, (float)TREE_SCALE);
glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, (float)TREE_SCALE);
return _cube.contains(clampedMin) && _cube.contains(clampedMax);
}
bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const {
glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, 1.0f);
glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, 1.0f);
glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, (float)TREE_SCALE);
glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, (float)TREE_SCALE);
if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) {
@ -823,9 +822,7 @@ bool EntityTreeElement::pruneChildren() {
void EntityTreeElement::debugDump() {
qDebug() << "EntityTreeElement...";
AACube temp = getAACube();
temp.scale((float)TREE_SCALE);
qDebug() << " cube:" << temp;
qDebug() << " cube:" << _cube;
qDebug() << " has child elements:" << getChildCount();
if (_entityItems->size()) {
qDebug() << " has entities:" << _entityItems->size();

View file

@ -23,6 +23,7 @@
#include "ModelEntityItem.h"
#include "SphereEntityItem.h"
#include "TextEntityItem.h"
#include "ParticleEffectEntityItem.h"
QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap;
QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap;
@ -37,6 +38,7 @@ REGISTER_ENTITY_TYPE(Box)
REGISTER_ENTITY_TYPE(Sphere)
REGISTER_ENTITY_TYPE(Light)
REGISTER_ENTITY_TYPE(Text)
REGISTER_ENTITY_TYPE(ParticleEffect)
const QString& EntityTypes::getEntityTypeName(EntityType entityType) {

View file

@ -35,7 +35,8 @@ public:
Sphere,
Light,
Text,
LAST = Text
ParticleEffect,
LAST = ParticleEffect
} EntityType;
static const QString& getEntityTypeName(EntityType entityType);

View file

@ -19,6 +19,7 @@
#include "ModelEntityItem.h"
const QString ModelEntityItem::DEFAULT_MODEL_URL = QString("");
const QString ModelEntityItem::DEFAULT_COLLISION_MODEL_URL = QString("");
const QString ModelEntityItem::DEFAULT_ANIMATION_URL = QString("");
const float ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f;
const bool ModelEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false;
@ -44,6 +45,7 @@ EntityItemProperties ModelEntityItem::getProperties() const {
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionModelURL, getCollisionModelURL);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationURL, getAnimationURL);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationIsPlaying, getAnimationIsPlaying);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFrameIndex, getAnimationFrameIndex);
@ -61,6 +63,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionModelURL, setCollisionModelURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationURL, setAnimationURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationIsPlaying, setAnimationIsPlaying);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex);
@ -92,6 +95,11 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL);
if (args.bitstreamVersion < VERSION_ENTITIES_HAS_COLLISION_MODEL) {
setCollisionModelURL("");
} else {
READ_ENTITY_PROPERTY_STRING(PROP_COLLISION_MODEL_URL, setCollisionModelURL);
}
READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL);
// Because we're using AnimationLoop which will reset the frame index if you change it's running state
@ -128,6 +136,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams&
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_MODEL_URL;
requestedProperties += PROP_COLLISION_MODEL_URL;
requestedProperties += PROP_ANIMATION_URL;
requestedProperties += PROP_ANIMATION_FPS;
requestedProperties += PROP_ANIMATION_FRAME_INDEX;
@ -151,6 +160,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, appendValue, getModelURL());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_MODEL_URL, appendValue, getCollisionModelURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, appendValue, getAnimationURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, getAnimationFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex());
@ -255,9 +265,10 @@ void ModelEntityItem::update(const quint64& now) {
void ModelEntityItem::debugDump() const {
qDebug() << "ModelEntityItem id:" << getEntityItemID();
qDebug() << " edited ago:" << getEditedAgo();
qDebug() << " position:" << getPosition() * (float)TREE_SCALE;
qDebug() << " dimensions:" << getDimensions() * (float)TREE_SCALE;
qDebug() << " position:" << getPosition();
qDebug() << " dimensions:" << getDimensions();
qDebug() << " model URL:" << getModelURL();
qDebug() << " collision model URL:" << getCollisionModelURL();
}
void ModelEntityItem::updateShapeType(ShapeType type) {

View file

@ -57,10 +57,14 @@ public:
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
bool hasModel() const { return !_modelURL.isEmpty(); }
bool hasCollisionModel() const { return !_collisionModelURL.isEmpty(); }
static const QString DEFAULT_MODEL_URL;
const QString& getModelURL() const { return _modelURL; }
static const QString DEFAULT_COLLISION_MODEL_URL;
const QString& getCollisionModelURL() const { return _collisionModelURL; }
bool hasAnimation() const { return !_animationURL.isEmpty(); }
static const QString DEFAULT_ANIMATION_URL;
const QString& getAnimationURL() const { return _animationURL; }
@ -74,6 +78,7 @@ public:
// model related properties
void setModelURL(const QString& url) { _modelURL = url; }
void setCollisionModelURL(const QString& url) { _collisionModelURL = url; }
void setAnimationURL(const QString& url);
static const float DEFAULT_ANIMATION_FRAME_INDEX;
void setAnimationFrameIndex(float value);
@ -121,6 +126,7 @@ protected:
rgbColor _color;
QString _modelURL;
QString _collisionModelURL;
quint64 _lastAnimated;
QString _animationURL;

View file

@ -51,7 +51,7 @@ MovingEntitiesOperator::~MovingEntitiesOperator() {
void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACube& newCube) {
EntityTreeElement* oldContainingElement = _tree->getContainingElement(entity->getEntityItemID());
AABox newCubeClamped = newCube.clamp(0.0f, 1.0f);
AABox newCubeClamped = newCube.clamp(0.0f, (float)TREE_SCALE);
if (_wantDebug) {
qDebug() << "MovingEntitiesOperator::addEntityToMoveList() -----------------------------";
@ -114,7 +114,7 @@ bool MovingEntitiesOperator::shouldRecurseSubTree(OctreeElement* element) {
// If we don't have an old entity, then we don't contain the entity, otherwise
// check the bounds
if (_entitiesToMove.size() > 0) {
AACube elementCube = element->getAACube();
const AACube& elementCube = element->getAACube();
int detailIndex = 0;
foreach(const EntityToMoveDetails& details, _entitiesToMove) {

View file

@ -15,11 +15,11 @@
class EntityToMoveDetails {
public:
EntityItem* entity;
AACube oldCube;
AACube newCube;
AABox newCubeClamped;
AACube oldCube; // meters
AACube newCube; // meters
AABox newCubeClamped; // meters
EntityTreeElement* oldContainingElement;
AACube oldContainingElementCube;
AACube oldContainingElementCube; // meters
bool oldFound;
bool newFound;
};

View file

@ -0,0 +1,512 @@
//
// ParticleEffectEntityItem.cpp
// libraries/entities/src
//
// Some starter code for a particle simulation entity, which could ideally be used for a variety of effects.
// This is some really early and rough stuff here. It was enough for now to just get it up and running in the interface.
//
// Todo's and other notes:
// - The simulation should restart when the AnimationLoop's max frame is reached (or passed), but there doesn't seem
// to be a good way to set that max frame to something reasonable right now.
// - There seems to be a bug whereby entities on the edge of screen will just pop off or on. This is probably due
// to my lack of understanding of how entities in the octree are picked for rendering. I am updating the entity
// dimensions based on the bounds of the sim, but maybe I need to update a dirty flag or something.
// - This should support some kind of pre-roll of the simulation.
// - Just to get this out the door, I just did forward Euler integration. There are better ways.
// - Gravity always points along the Y axis. Support an actual gravity vector.
// - Add the ability to add arbitrary forces to the simulation.
// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented).
// - Add drag.
// - Add some kind of support for collisions.
// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd
// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these
// should support animated textures.
// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so
// there's no gaurantee that different clients will see simulations that look anything like the other.
// - MORE?
//
// Created by Jason Rickwald on 3/2/15.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <glm/gtx/transform.hpp>
#include <QtCore/QJsonDocument>
#include <QDebug>
#include <ByteCountCoding.h>
#include <GeometryUtil.h>
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "ParticleEffectEntityItem.h"
const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f;
const bool ParticleEffectEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false;
const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FPS = 30.0f;
const quint32 ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES = 1000;
const float ParticleEffectEntityItem::DEFAULT_LIFESPAN = 3.0f;
const float ParticleEffectEntityItem::DEFAULT_EMIT_RATE = 15.0f;
const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION(0.0f, 1.0f, 0.0f);
const float ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH = 25.0f;
const float ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY = -9.8f;
const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f;
EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new ParticleEffectEntityItem(entityID, properties);
}
// our non-pure virtual subclass for now...
ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties) {
_type = EntityTypes::ParticleEffect;
_maxParticles = DEFAULT_MAX_PARTICLES;
_lifespan = DEFAULT_LIFESPAN;
_emitRate = DEFAULT_EMIT_RATE;
_emitDirection = DEFAULT_EMIT_DIRECTION;
_emitStrength = DEFAULT_EMIT_STRENGTH;
_localGravity = DEFAULT_LOCAL_GRAVITY;
_particleRadius = DEFAULT_PARTICLE_RADIUS;
setProperties(properties);
// this is a pretty dumb thing to do, and it should probably be changed to use a more dynamic
// data structure in the future. I'm just trying to get some code out the door for now (and it's
// at least time efficient (though not space efficient).
// Also, this being a real-time application, it's doubtful we'll ever have millions of particles
// to keep track of, so this really isn't all that bad.
_paLife = new float[_maxParticles];
_paPosition = new float[_maxParticles * XYZ_STRIDE]; // x,y,z
_paVelocity = new float[_maxParticles * XYZ_STRIDE]; // x,y,z
_paXmax = _paYmax = _paZmax = 1.0f;
_paXmin = _paYmin = _paZmin = -1.0f;
_randSeed = (unsigned int) glm::abs(_lifespan + _emitRate + _localGravity + getPosition().x + getPosition().y + getPosition().z);
resetSimulation();
_lastAnimated = usecTimestampNow();
}
ParticleEffectEntityItem::~ParticleEffectEntityItem() {
delete [] _paLife;
delete [] _paPosition;
delete [] _paVelocity;
}
EntityItemProperties ParticleEffectEntityItem::getProperties() const {
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationIsPlaying, getAnimationIsPlaying);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFrameIndex, getAnimationFrameIndex);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFPS, getAnimationFPS);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationSettings, getAnimationSettings);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifespan, getLifespan);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRate, getEmitRate);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitDirection, getEmitDirection);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitStrength, getEmitStrength);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localGravity, getLocalGravity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius);
return properties;
}
bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationIsPlaying, setAnimationIsPlaying);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFPS, setAnimationFPS);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationSettings, setAnimationSettings);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRate, setEmitRate);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitDirection, setEmitDirection);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitStrength, setEmitStrength);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(localGravity, setLocalGravity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius);
if (somethingChanged) {
bool wantDebug = false;
if (wantDebug) {
uint64_t now = usecTimestampNow();
int elapsed = now - getLastEdited();
qDebug() << "ParticleEffectEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " getLastEdited()=" << getLastEdited();
}
setLastEdited(properties.getLastEdited());
}
return somethingChanged;
}
int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
// Because we're using AnimationLoop which will reset the frame index if you change it's running state
// we want to read these values in the order they appear in the buffer, but call our setters in an
// order that allows AnimationLoop to preserve the correct frame rate.
float animationFPS = getAnimationFPS();
float animationFrameIndex = getAnimationFrameIndex();
bool animationIsPlaying = getAnimationIsPlaying();
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex);
READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying);
if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) {
if (animationIsPlaying != getAnimationIsPlaying()) {
setAnimationIsPlaying(animationIsPlaying);
}
}
if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) {
setAnimationFPS(animationFPS);
}
if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) {
setAnimationFrameIndex(animationFrameIndex);
}
READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_SETTINGS, setAnimationSettings);
READ_ENTITY_PROPERTY_SETTER(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, _maxParticles);
READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, _lifespan);
READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, _emitRate);
READ_ENTITY_PROPERTY_SETTER(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection);
READ_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, float, _emitStrength);
READ_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, float, _localGravity);
READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, _particleRadius);
return bytesRead;
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;
requestedProperties += PROP_ANIMATION_FPS;
requestedProperties += PROP_ANIMATION_FRAME_INDEX;
requestedProperties += PROP_ANIMATION_PLAYING;
requestedProperties += PROP_ANIMATION_SETTINGS;
requestedProperties += PROP_SHAPE_TYPE;
requestedProperties += PROP_MAX_PARTICLES;
requestedProperties += PROP_LIFESPAN;
requestedProperties += PROP_EMIT_RATE;
requestedProperties += PROP_EMIT_DIRECTION;
requestedProperties += PROP_EMIT_STRENGTH;
requestedProperties += PROP_LOCAL_GRAVITY;
requestedProperties += PROP_PARTICLE_RADIUS;
return requestedProperties;
}
void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, getAnimationFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, getAnimationIsPlaying());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, appendValue, getAnimationSettings());
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, appendValue, (uint32_t)getShapeType());
APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, appendValue, getMaxParticles());
APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, appendValue, getLifespan());
APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, appendValue, getEmitRate());
APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, appendValue, getEmitDirection());
APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, getEmitStrength());
APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, getLocalGravity());
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, getParticleRadius());
}
bool ParticleEffectEntityItem::isAnimatingSomething() const {
return getAnimationIsPlaying() &&
getAnimationFPS() != 0.0f;
}
bool ParticleEffectEntityItem::needsToCallUpdate() const {
return isAnimatingSomething() ? true : EntityItem::needsToCallUpdate();
}
void ParticleEffectEntityItem::update(const quint64& now) {
// only advance the frame index if we're playing
if (getAnimationIsPlaying()) {
float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND;
_lastAnimated = now;
float lastFrame = _animationLoop.getFrameIndex();
_animationLoop.simulate(deltaTime);
float curFrame = _animationLoop.getFrameIndex();
if (curFrame > lastFrame) {
stepSimulation(deltaTime);
}
else if (curFrame < lastFrame) {
// we looped around, so restart the sim and only sim up to the point
// since the beginning of the frame range.
resetSimulation();
stepSimulation((curFrame - _animationLoop.getFirstFrame()) / _animationLoop.getFPS());
}
}
else {
_lastAnimated = now;
}
// update the dimensions
glm::vec3 dims;
dims.x = glm::max(glm::abs(_paXmin), glm::abs(_paXmax)) * 2.0f;
dims.y = glm::max(glm::abs(_paYmin), glm::abs(_paYmax)) * 2.0f;
dims.z = glm::max(glm::abs(_paZmin), glm::abs(_paZmax)) * 2.0f;
setDimensions(dims);
EntityItem::update(now); // let our base class handle it's updates...
}
void ParticleEffectEntityItem::debugDump() const {
quint64 now = usecTimestampNow();
qDebug() << "PA EFFECT EntityItem id:" << getEntityItemID() << "---------------------------------------------";
qDebug() << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
qDebug() << " position:" << debugTreeVector(_position);
qDebug() << " dimensions:" << debugTreeVector(_dimensions);
qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now);
}
void ParticleEffectEntityItem::updateShapeType(ShapeType type) {
if (type != _shapeType) {
_shapeType = type;
_dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS;
}
}
void ParticleEffectEntityItem::setAnimationFrameIndex(float value) {
#ifdef WANT_DEBUG
if (isAnimatingSomething()) {
qDebug() << "ParticleEffectEntityItem::setAnimationFrameIndex()";
qDebug() << " value:" << value;
qDebug() << " was:" << _animationLoop.getFrameIndex();
}
#endif
_animationLoop.setFrameIndex(value);
}
void ParticleEffectEntityItem::setAnimationSettings(const QString& value) {
// the animations setting is a JSON string that may contain various animation settings.
// if it includes fps, frameIndex, or running, those values will be parsed out and
// will over ride the regular animation settings
QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
QJsonObject settingsAsJsonObject = settingsAsJson.object();
QVariantMap settingsMap = settingsAsJsonObject.toVariantMap();
if (settingsMap.contains("fps")) {
float fps = settingsMap["fps"].toFloat();
setAnimationFPS(fps);
}
if (settingsMap.contains("frameIndex")) {
float frameIndex = settingsMap["frameIndex"].toFloat();
#ifdef WANT_DEBUG
if (isAnimatingSomething()) {
qDebug() << "ParticleEffectEntityItem::setAnimationSettings() calling setAnimationFrameIndex()...";
qDebug() << " settings:" << value;
qDebug() << " settingsMap[frameIndex]:" << settingsMap["frameIndex"];
qDebug(" frameIndex: %20.5f", frameIndex);
}
#endif
setAnimationFrameIndex(frameIndex);
}
if (settingsMap.contains("running")) {
bool running = settingsMap["running"].toBool();
if (running != getAnimationIsPlaying()) {
setAnimationIsPlaying(running);
}
}
if (settingsMap.contains("firstFrame")) {
float firstFrame = settingsMap["firstFrame"].toFloat();
setAnimationFirstFrame(firstFrame);
}
if (settingsMap.contains("lastFrame")) {
float lastFrame = settingsMap["lastFrame"].toFloat();
setAnimationLastFrame(lastFrame);
}
if (settingsMap.contains("loop")) {
bool loop = settingsMap["loop"].toBool();
setAnimationLoop(loop);
}
if (settingsMap.contains("hold")) {
bool hold = settingsMap["hold"].toBool();
setAnimationHold(hold);
}
if (settingsMap.contains("startAutomatically")) {
bool startAutomatically = settingsMap["startAutomatically"].toBool();
setAnimationStartAutomatically(startAutomatically);
}
_animationSettings = value;
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
}
void ParticleEffectEntityItem::setAnimationIsPlaying(bool value) {
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
_animationLoop.setRunning(value);
}
void ParticleEffectEntityItem::setAnimationFPS(float value) {
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
_animationLoop.setFPS(value);
}
QString ParticleEffectEntityItem::getAnimationSettings() const {
// the animations setting is a JSON string that may contain various animation settings.
// if it includes fps, frameIndex, or running, those values will be parsed out and
// will over ride the regular animation settings
QString value = _animationSettings;
QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
QJsonObject settingsAsJsonObject = settingsAsJson.object();
QVariantMap settingsMap = settingsAsJsonObject.toVariantMap();
QVariant fpsValue(getAnimationFPS());
settingsMap["fps"] = fpsValue;
QVariant frameIndexValue(getAnimationFrameIndex());
settingsMap["frameIndex"] = frameIndexValue;
QVariant runningValue(getAnimationIsPlaying());
settingsMap["running"] = runningValue;
QVariant firstFrameValue(getAnimationFirstFrame());
settingsMap["firstFrame"] = firstFrameValue;
QVariant lastFrameValue(getAnimationLastFrame());
settingsMap["lastFrame"] = lastFrameValue;
QVariant loopValue(getAnimationLoop());
settingsMap["loop"] = loopValue;
QVariant holdValue(getAnimationHold());
settingsMap["hold"] = holdValue;
QVariant startAutomaticallyValue(getAnimationStartAutomatically());
settingsMap["startAutomatically"] = startAutomaticallyValue;
settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap);
QJsonDocument newDocument(settingsAsJsonObject);
QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact);
QString jsonByteString(jsonByteArray);
return jsonByteString;
}
void ParticleEffectEntityItem::stepSimulation(float deltaTime) {
_paXmin = _paYmin = _paZmin = -1.0f;
_paXmax = _paYmax = _paZmax = 1.0f;
// update particles
quint32 updateIter = _paHead;
while (_paLife[updateIter] > 0.0f) {
_paLife[updateIter] -= deltaTime;
if (_paLife[updateIter] <= 0.0f) {
_paLife[updateIter] = -1.0f;
_paHead = (_paHead + 1) % _maxParticles;
_paCount--;
}
else {
// DUMB FORWARD EULER just to get it done
int j = updateIter * XYZ_STRIDE;
_paPosition[j] += _paVelocity[j] * deltaTime;
_paPosition[j+1] += _paVelocity[j+1] * deltaTime;
_paPosition[j+2] += _paVelocity[j+2] * deltaTime;
_paXmin = glm::min(_paXmin, _paPosition[j]);
_paYmin = glm::min(_paYmin, _paPosition[j+1]);
_paZmin = glm::min(_paZmin, _paPosition[j+2]);
_paXmax = glm::max(_paXmax, _paPosition[j]);
_paYmax = glm::max(_paYmax, _paPosition[j + 1]);
_paZmax = glm::max(_paZmax, _paPosition[j + 2]);
// massless particles
_paVelocity[j + 1] += deltaTime * _localGravity;
}
updateIter = (updateIter + 1) % _maxParticles;
}
// emit new particles
quint32 emitIdx = updateIter;
_partialEmit += ((float)_emitRate) * deltaTime;
quint32 birthed = (quint32)_partialEmit;
_partialEmit -= (float)birthed;
glm::vec3 randOffset;
for (quint32 i = 0; i < birthed; i++) {
if (_paLife[emitIdx] < 0.0f) {
int j = emitIdx * XYZ_STRIDE;
_paLife[emitIdx] = _lifespan;
randOffset.x = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
randOffset.y = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
randOffset.z = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
_paVelocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x;
_paVelocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y;
_paVelocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z;
// DUMB FORWARD EULER just to get it done
_paPosition[j] += _paVelocity[j] * deltaTime;
_paPosition[j + 1] += _paVelocity[j + 1] * deltaTime;
_paPosition[j + 2] += _paVelocity[j + 2] * deltaTime;
_paXmin = glm::min(_paXmin, _paPosition[j]);
_paYmin = glm::min(_paYmin, _paPosition[j + 1]);
_paZmin = glm::min(_paZmin, _paPosition[j + 2]);
_paXmax = glm::max(_paXmax, _paPosition[j]);
_paYmax = glm::max(_paYmax, _paPosition[j + 1]);
_paZmax = glm::max(_paZmax, _paPosition[j + 2]);
// massless particles
// and simple gravity down
_paVelocity[j + 1] += deltaTime * _localGravity;
emitIdx = (emitIdx + 1) % _maxParticles;
_paCount++;
}
else
break;
}
}
void ParticleEffectEntityItem::resetSimulation() {
for (quint32 i = 0; i < _maxParticles; i++) {
quint32 j = i * XYZ_STRIDE;
_paLife[i] = -1.0f;
_paPosition[j] = 0.0f;
_paPosition[j+1] = 0.0f;
_paPosition[j+2] = 0.0f;
_paVelocity[j] = 0.0f;
_paVelocity[j+1] = 0.0f;
_paVelocity[j+2] = 0.0f;
}
_paCount = 0;
_paHead = 0;
_partialEmit = 0.0f;
srand(_randSeed);
}

View file

@ -0,0 +1,183 @@
//
// ParticleEffectEntityItem.h
// libraries/entities/src
//
// Some starter code for a particle simulation entity, which could ideally be used for a variety of effects.
// This is some really early and rough stuff here. It was enough for now to just get it up and running in the interface.
//
// Todo's and other notes:
// - The simulation should restart when the AnimationLoop's max frame is reached (or passed), but there doesn't seem
// to be a good way to set that max frame to something reasonable right now.
// - There seems to be a bug whereby entities on the edge of screen will just pop off or on. This is probably due
// to my lack of understanding of how entities in the octree are picked for rendering. I am updating the entity
// dimensions based on the bounds of the sim, but maybe I need to update a dirty flag or something.
// - This should support some kind of pre-roll of the simulation.
// - Just to get this out the door, I just did forward Euler integration. There are better ways.
// - Gravity always points along the Y axis. Support an actual gravity vector.
// - Add the ability to add arbitrary forces to the simulation.
// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented).
// - Add drag.
// - Add some kind of support for collisions.
// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd
// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these
// should support animated textures.
// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so
// there's no gaurantee that different clients will see simulations that look anything like the other.
// - MORE?
//
// Created by Jason Rickwald on 3/2/15.
//
// 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_ParticleEffectEntityItem_h
#define hifi_ParticleEffectEntityItem_h
#include <AnimationLoop.h>
#include "EntityItem.h"
class ParticleEffectEntityItem : public EntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
virtual ~ParticleEffectEntityItem();
ALLOW_INSTANTIATION // This class can be instantiated
// methods for getting/setting all properties of this entity
virtual EntityItemProperties getProperties() const;
virtual bool setProperties(const EntityItemProperties& properties);
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const;
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
virtual void update(const quint64& now);
virtual bool needsToCallUpdate() const;
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
void setColor(const xColor& value) {
_color[RED_INDEX] = value.red;
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
void updateShapeType(ShapeType type);
virtual ShapeType getShapeType() const { return _shapeType; }
virtual void debugDump() const;
static const float DEFAULT_ANIMATION_FRAME_INDEX;
void setAnimationFrameIndex(float value);
void setAnimationSettings(const QString& value);
static const bool DEFAULT_ANIMATION_IS_PLAYING;
void setAnimationIsPlaying(bool value);
static const float DEFAULT_ANIMATION_FPS;
void setAnimationFPS(float value);
void setAnimationLoop(bool loop) { _animationLoop.setLoop(loop); }
bool getAnimationLoop() const { return _animationLoop.getLoop(); }
void setAnimationHold(bool hold) { _animationLoop.setHold(hold); }
bool getAnimationHold() const { return _animationLoop.getHold(); }
void setAnimationStartAutomatically(bool startAutomatically) { _animationLoop.setStartAutomatically(startAutomatically); }
bool getAnimationStartAutomatically() const { return _animationLoop.getStartAutomatically(); }
void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); }
float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); }
void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); }
float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); }
static const quint32 DEFAULT_MAX_PARTICLES;
void setMaxParticles(quint32 maxParticles) { _maxParticles = maxParticles; }
quint32 getMaxParticles() const { return _maxParticles; }
static const float DEFAULT_LIFESPAN;
void setLifespan(float lifespan) { _lifespan = lifespan; }
float getLifespan() const { return _lifespan; }
static const float DEFAULT_EMIT_RATE;
void setEmitRate(float emitRate) { _emitRate = emitRate; }
float getEmitRate() const { return _emitRate; }
static const glm::vec3 DEFAULT_EMIT_DIRECTION;
void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = emitDirection; }
const glm::vec3& getEmitDirection() const { return _emitDirection; }
static const float DEFAULT_EMIT_STRENGTH;
void setEmitStrength(float emitStrength) { _emitStrength = emitStrength; }
float getEmitStrength() const { return _emitStrength; }
static const float DEFAULT_LOCAL_GRAVITY;
void setLocalGravity(float localGravity) { _localGravity = localGravity; }
float getLocalGravity() const { return _localGravity; }
static const float DEFAULT_PARTICLE_RADIUS;
void setParticleRadius(float particleRadius) { _particleRadius = particleRadius; }
float getParticleRadius() const { return _particleRadius; }
bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); }
float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); }
float getAnimationFPS() const { return _animationLoop.getFPS(); }
QString getAnimationSettings() const;
protected:
bool isAnimatingSomething() const;
void stepSimulation(float deltaTime);
void resetSimulation();
// the properties of this entity
rgbColor _color;
quint32 _maxParticles;
float _lifespan;
float _emitRate;
glm::vec3 _emitDirection;
float _emitStrength;
float _localGravity;
float _particleRadius;
quint64 _lastAnimated;
AnimationLoop _animationLoop;
QString _animationSettings;
ShapeType _shapeType = SHAPE_TYPE_NONE;
// all the internals of running the particle sim
const quint32 XYZ_STRIDE = 3;
float* _paLife;
float* _paPosition;
float* _paVelocity;
float _partialEmit;
quint32 _paCount;
quint32 _paHead;
float _paXmin;
float _paXmax;
float _paYmin;
float _paYmax;
float _paZmin;
float _paZmax;
unsigned int _randSeed;
};
#endif // hifi_ParticleEffectEntityItem_h

View file

@ -32,9 +32,6 @@ SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const Entit
{
_type = EntityTypes::Sphere;
setProperties(properties);
// NOTE: _volumeMultiplier is used to compute volume:
// volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z
// The formula below looks funny because _dimension.xyz = diameter rather than radius.
_volumeMultiplier *= PI / 6.0f;
}
@ -117,7 +114,7 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons
distance = glm::distance(origin,hitAt);
return true;
}
return false;
return false;
}

View file

@ -40,10 +40,16 @@ TextEntityItem::TextEntityItem(const EntityItemID& entityItemID, const EntityIte
setProperties(properties);
}
const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
void TextEntityItem::setDimensions(const glm::vec3& value) {
// NOTE: Text Entities always have a "depth" of 1cm.
float fixedDepth = 0.01f / (float)TREE_SCALE;
_dimensions = glm::vec3(value.x, value.y, fixedDepth);
_dimensions = glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH);
}
void TextEntityItem::setDimensionsInDomainUnits(const glm::vec3& value) {
// NOTE: Text Entities always have a "depth" of 1cm.
_dimensions = glm::vec3(value.x * (float)TREE_SCALE, value.y * (float)TREE_SCALE, TEXT_ENTITY_ITEM_FIXED_DEPTH);
}
EntityItemProperties TextEntityItem::getProperties() const {
@ -136,7 +142,7 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f);
glm::vec3 normal = _rotation * UNROTATED_NORMAL;
plane.setNormal(normal);
plane.setPoint(_position); // the position is definitely a point on our plane
plane.setPoint(getPosition()); // the position is definitely a point on our plane
bool intersects = plane.findRayIntersection(rayInfo);

View file

@ -24,6 +24,7 @@ public:
/// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately
virtual void setDimensions(const glm::vec3& value);
virtual void setDimensionsInDomainUnits(const glm::vec3& value);
virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; }
// methods for getting/setting all properties of an entity

View file

@ -46,7 +46,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree,
// which can handle all potential rotations?
// the getMaximumAACube is the relaxed form.
_oldEntityCube = _existingEntity->getMaximumAACube();
_oldEntityBox = _oldEntityCube.clamp(0.0f, 1.0f); // clamp to domain bounds
_oldEntityBox = _oldEntityCube.clamp(0.0f, (float)TREE_SCALE); // clamp to domain bounds
// If the old properties doesn't contain the properties required to calculate a bounding box,
// get them from the existing entity. Registration point is required to correctly calculate
@ -59,8 +59,8 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree,
// get the old property value and set it in our properties in order for our bounds
// calculations to work.
if (_properties.containsPositionChange() && !_properties.containsDimensionsChange()) {
glm::vec3 oldDimensionsInMeters = _existingEntity->getDimensions() * (float)TREE_SCALE;
_properties.setDimensions(oldDimensionsInMeters);
glm::vec3 oldDimensions= _existingEntity->getDimensions();
_properties.setDimensions(oldDimensions);
if (_wantDebug) {
qDebug() << " ** setting properties dimensions - had position change, no dimension change **";
@ -68,8 +68,8 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree,
}
if (!_properties.containsPositionChange() && _properties.containsDimensionsChange()) {
glm::vec3 oldPositionInMeters = _existingEntity->getPosition() * (float)TREE_SCALE;
_properties.setPosition(oldPositionInMeters);
glm::vec3 oldPosition= _existingEntity->getPosition();
_properties.setPosition(oldPosition);
if (_wantDebug) {
qDebug() << " ** setting properties position - had dimensions change, no position change **";
@ -114,7 +114,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree,
}
} else {
_newEntityCube = _properties.getMaximumAACubeInTreeUnits();
_newEntityCube = _properties.getMaximumAACube();
_removeOld = true; // our properties are going to move us, so remember this for later processing
if (_wantDebug) {
@ -122,7 +122,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree,
}
}
_newEntityBox = _newEntityCube.clamp(0.0f, 1.0f); // clamp to domain bounds
_newEntityBox = _newEntityCube.clamp(0.0f, (float)TREE_SCALE); // clamp to domain bounds
if (_wantDebug) {

View file

@ -295,7 +295,11 @@ public:
Tokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { }
enum SpecialToken { DATUM_TOKEN = 0x100 };
enum SpecialToken {
NO_TOKEN = -1,
NO_PUSHBACKED_TOKEN = -1,
DATUM_TOKEN = 0x100
};
int nextToken();
const QByteArray& getDatum() const { return _datum; }
@ -311,9 +315,9 @@ private:
};
int Tokenizer::nextToken() {
if (_pushedBackToken != -1) {
if (_pushedBackToken != NO_PUSHBACKED_TOKEN) {
int token = _pushedBackToken;
_pushedBackToken = -1;
_pushedBackToken = NO_PUSHBACKED_TOKEN;
return token;
}
@ -361,7 +365,7 @@ int Tokenizer::nextToken() {
return DATUM_TOKEN;
}
}
return -1;
return NO_TOKEN;
}
FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
@ -378,7 +382,7 @@ FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
int token;
bool expectingDatum = true;
while ((token = tokenizer.nextToken()) != -1) {
while ((token = tokenizer.nextToken()) != Tokenizer::NO_TOKEN) {
if (token == '{') {
for (FBXNode child = parseTextFBXNode(tokenizer); !child.name.isNull(); child = parseTextFBXNode(tokenizer)) {
node.children.append(child);

View file

@ -152,8 +152,6 @@ public:
bool hasSpecularTexture() const;
bool hasEmissiveTexture() const;
model::Mesh _mesh;
};
/// A single animation frame extracted from an FBX document.

View file

@ -0,0 +1,287 @@
//
// OBJReader.cpp
// libraries/fbx/src/
//
// Created by Seth Alves on 3/7/15.
// Copyright 2013 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://en.wikipedia.org/wiki/Wavefront_.obj_file
// http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/obj-file-format/obj-file-format/
// http://paulbourke.net/dataformats/obj/
#include <QBuffer>
#include <QIODevice>
#include "FBXReader.h"
#include "OBJReader.h"
#include "Shape.h"
class OBJTokenizer {
public:
OBJTokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { }
enum SpecialToken {
NO_TOKEN = -1,
NO_PUSHBACKED_TOKEN = -1,
DATUM_TOKEN = 0x100
};
int nextToken();
const QByteArray& getDatum() const { return _datum; }
void skipLine() { _device->readLine(); }
void pushBackToken(int token) { _pushedBackToken = token; }
void ungetChar(char ch) { _device->ungetChar(ch); }
private:
QIODevice* _device;
QByteArray _datum;
int _pushedBackToken;
};
int OBJTokenizer::nextToken() {
if (_pushedBackToken != NO_PUSHBACKED_TOKEN) {
int token = _pushedBackToken;
_pushedBackToken = NO_PUSHBACKED_TOKEN;
return token;
}
char ch;
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace()) {
continue; // skip whitespace
}
switch (ch) {
case '#':
_device->readLine(); // skip the comment
break;
case '\"':
_datum = "";
while (_device->getChar(&ch)) {
if (ch == '\"') { // end on closing quote
break;
}
if (ch == '\\') { // handle escaped quotes
if (_device->getChar(&ch) && ch != '\"') {
_datum.append('\\');
}
}
_datum.append(ch);
}
return DATUM_TOKEN;
default:
_datum = "";
_datum.append(ch);
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace() || ch == '\"') {
ungetChar(ch); // read until we encounter a special character, then replace it
break;
}
_datum.append(ch);
}
return DATUM_TOKEN;
}
}
return NO_TOKEN;
}
bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, FBXGeometry &geometry) {
FBXMesh &mesh = geometry.meshes[0];
mesh.parts.append(FBXMeshPart());
FBXMeshPart &meshPart = mesh.parts.last();
bool sawG = false;
bool result = true;
meshPart.materialID = QString("dontknow") + QString::number(mesh.parts.count());
meshPart.opacity = 1.0;
meshPart._material = model::MaterialPointer(new model::Material());
meshPart._material->setDiffuse(glm::vec3(1.0, 1.0, 1.0));
meshPart._material->setOpacity(1.0);
meshPart._material->setSpecular(glm::vec3(1.0, 1.0, 1.0));
meshPart._material->setShininess(96.0);
meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0));
while (true) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
result = false;
break;
}
QByteArray token = tokenizer.getDatum();
if (token == "g") {
if (sawG) {
// we've encountered the beginning of the next group.
tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
break;
}
sawG = true;
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
QByteArray groupName = tokenizer.getDatum();
meshPart.materialID = groupName;
} else if (token == "v") {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
float x = std::stof(tokenizer.getDatum().data());
// notice the order of z and y -- in OBJ files, up is the 3rd value
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
float z = std::stof(tokenizer.getDatum().data());
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
float y = std::stof(tokenizer.getDatum().data());
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
// the spec gets vague here. might be w, might be a color... chop it off.
tokenizer.skipLine();
mesh.vertices.append(glm::vec3(x, y, z));
mesh.colors.append(glm::vec3(1, 1, 1));
} else if (token == "f") {
// a face can have 3 or more vertices
QVector<int> indices;
while (true) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { goto done; }
try {
int vertexIndex = std::stoi(tokenizer.getDatum().data());
// negative indexes count backward from the current end of the vertex list
vertexIndex = (vertexIndex >= 0 ? vertexIndex : mesh.vertices.count() + vertexIndex + 1);
// obj index is 1 based
assert(vertexIndex >= 1);
indices.append(vertexIndex - 1);
}
catch(const std::exception& e) {
// wasn't a number, but it back.
tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
break;
}
}
if (indices.count() == 3) {
// flip these around (because of the y/z swap above) so our triangles face outward
meshPart.triangleIndices.append(indices[0]);
meshPart.triangleIndices.append(indices[2]);
meshPart.triangleIndices.append(indices[1]);
} else if (indices.count() == 4) {
meshPart.quadIndices << indices;
} else {
qDebug() << "no support for more than 4 vertices on a face in OBJ files";
}
} else {
// something we don't (yet) care about
qDebug() << "OBJ parser is skipping a line with" << token;
tokenizer.skipLine();
}
}
done:
return result;
}
FBXGeometry extractOBJGeometry(const FBXNode& node, const QVariantHash& mapping) {
FBXGeometry geometry;
return geometry;
}
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
return readOBJ(&buffer, mapping);
}
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
FBXGeometry geometry;
OBJTokenizer tokenizer(device);
geometry.meshExtents.reset();
geometry.meshes.append(FBXMesh());
try {
// call parseOBJGroup as long as it's returning true. Each successful call will
// add a new meshPart to the geometry's single mesh.
bool success = true;
while (success) {
success = parseOBJGroup(tokenizer, mapping, geometry);
}
FBXMesh &mesh = geometry.meshes[0];
mesh.meshExtents.reset();
foreach (const glm::vec3& vertex, mesh.vertices) {
mesh.meshExtents.addPoint(vertex);
geometry.meshExtents.addPoint(vertex);
}
geometry.joints.resize(1);
geometry.joints[0].isFree = false;
// geometry.joints[0].freeLineage;
geometry.joints[0].parentIndex = -1;
geometry.joints[0].distanceToParent = 0;
geometry.joints[0].boneRadius = 0;
geometry.joints[0].translation = glm::vec3(0, 0, 0);
// geometry.joints[0].preTransform = ;
geometry.joints[0].preRotation = glm::quat(0, 0, 0, 1);
geometry.joints[0].rotation = glm::quat(0, 0, 0, 1);
geometry.joints[0].postRotation = glm::quat(0, 0, 0, 1);
// geometry.joints[0].postTransform = ;
// geometry.joints[0].transform = ;
geometry.joints[0].rotationMin = glm::vec3(0, 0, 0);
geometry.joints[0].rotationMax = glm::vec3(0, 0, 0);
geometry.joints[0].inverseDefaultRotation = glm::quat(0, 0, 0, 1);
geometry.joints[0].inverseBindRotation = glm::quat(0, 0, 0, 1);
// geometry.joints[0].bindTransform = ;
geometry.joints[0].name = "OBJ";
geometry.joints[0].shapePosition = glm::vec3(0, 0, 0);
geometry.joints[0].shapeRotation = glm::quat(0, 0, 0, 1);
geometry.joints[0].shapeType = SPHERE_SHAPE;
geometry.joints[0].isSkeletonJoint = false;
// add bogus normal data for this mesh
mesh.normals.fill(glm::vec3(0,0,0), mesh.vertices.count());
mesh.tangents.fill(glm::vec3(0,0,0), mesh.vertices.count());
foreach (FBXMeshPart meshPart, mesh.parts) {
int triCount = meshPart.triangleIndices.count() / 3;
for (int i = 0; i < triCount; i++) {
int p0Index = meshPart.triangleIndices[i*3];
int p1Index = meshPart.triangleIndices[i*3+1];
int p2Index = meshPart.triangleIndices[i*3+2];
glm::vec3 p0 = mesh.vertices[p0Index];
glm::vec3 p1 = mesh.vertices[p1Index];
glm::vec3 p2 = mesh.vertices[p2Index];
glm::vec3 n = glm::cross(p1 - p0, p2 - p0);
glm::vec3 t = glm::cross(p2 - p0, n);
mesh.normals[p0Index] = n;
mesh.normals[p1Index] = n;
mesh.normals[p2Index] = n;
mesh.tangents[p0Index] = t;
mesh.tangents[p1Index] = t;
mesh.tangents[p2Index] = t;
}
}
}
catch(const std::exception& e) {
qDebug() << "something went wrong in OBJ reader";
}
return geometry;
}

View file

@ -0,0 +1,6 @@
#include "FBXReader.h"
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping);

View file

@ -76,26 +76,26 @@ public:
void setOpacity(float opacity);
// Schema to access the attribute values of the material
class Schema {
public:
Color _diffuse;
float _opacity;
Color _specular;
float _shininess;
Color _emissive;
float _spare0;
Schema() :
_diffuse(0.5f),
_opacity(1.0f),
_specular(0.03f),
_shininess(0.1f),
_emissive(0.0f)
{}
class Schema {
public:
Color _diffuse;
float _opacity;
Color _specular;
float _shininess;
Color _emissive;
float _spare0;
Schema() :
_diffuse(0.5f),
_opacity(1.0f),
_specular(0.03f),
_shininess(0.1f),
_emissive(0.0f)
{}
};
const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; }
const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; }
void setTextureView(MapChannel channel, const TextureView& texture);
const TextureMap& getTextureMap() const { return _textureMap; }

View file

@ -26,14 +26,14 @@ class Assignment : public NodeData {
public:
enum Type {
AudioMixerType,
AvatarMixerType,
AgentType,
UNUSED_0,
UNUSED_1,
UNUSED_2,
EntityServerType,
AllTypes
AudioMixerType = 0,
AvatarMixerType = 1,
AgentType = 2,
UNUSED_0 = 3,
UNUSED_1 = 4,
UNUSED_2 = 5,
EntityServerType = 6,
AllTypes = 7
};
enum Command {

View file

@ -48,7 +48,8 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
_localSockAddr(),
_publicSockAddr(),
_stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT),
_packetStatTimer()
_packetStatTimer(),
_thisNodeCanAdjustLocks(false)
{
static bool firstCall = true;
if (firstCall) {

View file

@ -216,7 +216,6 @@ protected:
void handleNodeKill(const SharedNodePointer& node);
QUuid _sessionUUID;
bool _thisNodeCanAdjustLocks;
NodeHash _nodeHash;
QReadWriteLock _nodeMutex;
QUdpSocket _nodeSocket;
@ -230,6 +229,7 @@ protected:
int _numCollectedBytes;
QElapsedTimer _packetStatTimer;
bool _thisNodeCanAdjustLocks;
template<typename IteratorLambda>
void eachNodeHashIterator(IteratorLambda functor) {

View file

@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) {
return 1;
case PacketTypeEntityAddOrEdit:
case PacketTypeEntityData:
return VERSION_ENTITIES_LIGHT_HAS_INTENSITY_AND_COLOR_PROPERTIES;
return VERSION_ENTITIES_HAS_COLLISION_MODEL;
case PacketTypeEntityErase:
return 2;
case PacketTypeAudioStreamStats:

View file

@ -129,6 +129,9 @@ const PacketVersion VERSION_ENTITIES_HAVE_USER_DATA = 6;
const PacketVersion VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME = 7;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SHAPE_TYPE = 8;
const PacketVersion VERSION_ENTITIES_LIGHT_HAS_INTENSITY_AND_COLOR_PROPERTIES = 9;
const PacketVersion VERSION_ENTITIES_HAS_PARTICLES = 10;
const PacketVersion VERSION_ENTITIES_USE_METERS_AND_RADIANS = 11;
const PacketVersion VERSION_ENTITIES_HAS_COLLISION_MODEL = 12;
const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1;
#endif // hifi_PacketHeaders_h

View file

@ -12,9 +12,10 @@
#include <cfloat>
#include <cmath>
#include <QDebug>
#include <QNetworkDiskCache>
#include <QThread>
#include <QTimer>
#include <QtDebug>
#include <SharedUtil.h>
@ -314,13 +315,51 @@ void Resource::handleReplyTimeout() {
"received" << _bytesReceived << "total" << _bytesTotal);
}
void Resource::maybeRefresh() {
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
QVariant variant = reply->header(QNetworkRequest::LastModifiedHeader);
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
if (variant.isValid() && variant.canConvert<QDateTime>() && metaData.isValid()) {
QDateTime lastModified = variant.value<QDateTime>();
QDateTime lastModifiedOld = metaData.lastModified();
if (lastModified.isValid() && lastModifiedOld.isValid() &&
lastModifiedOld == lastModified) {
// We don't need to update, return
return;
}
}
qDebug() << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing.";
refresh();
}
}
void Resource::makeRequest() {
_reply = NetworkAccessManager::getInstance().get(_request);
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished()));
if (_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) {
// If the file as been updated since it was cached, refresh it
QNetworkRequest request(_request);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
QNetworkReply* reply = NetworkAccessManager::getInstance().head(request);
connect(reply, &QNetworkReply::finished, this, &Resource::maybeRefresh);
} else {
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) {
// If the expiration date is NULL or in the past,
// put one far enough away that it won't be an issue.
metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100));
NetworkAccessManager::getInstance().cache()->updateMetaData(metaData);
}
}
}
_replyTimer = new QTimer(this);
connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout()));
_replyTimer->setSingleShot(true);

View file

@ -149,7 +149,7 @@ public:
/// For loading resources, returns the load progress.
float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; }
/// Refreshes the resource.
void refresh();
@ -169,6 +169,10 @@ signals:
protected slots:
void attemptRequest();
/// Refreshes the resource if the last modified date on the network
/// is greater than the last modified date in the cache.
void maybeRefresh();
protected:

View file

@ -274,7 +274,7 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch
if (destinationElement->getScale() < SCALE_AT_DANGEROUSLY_DEEP_RECURSION) {
qDebug() << "UNEXPECTED: readElementData() destination element is unreasonably small ["
<< destinationElement->getScale() * (float)TREE_SCALE << " meters] "
<< destinationElement->getScale() << " meters] "
<< " Discarding " << bytesAvailable << " remaining bytes.";
return bytesAvailable; // assume we read the entire buffer...
}
@ -706,7 +706,7 @@ bool findRayIntersectionOp(OctreeElement* element, void* extraData) {
bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject,
Octree::lockType lockType, bool* accurateResult, bool precisionPicking) {
RayArgs args = { origin / (float)(TREE_SCALE), direction, element, distance, face,
RayArgs args = { origin, direction, element, distance, face,
intersectedObject, false, precisionPicking};
distance = FLT_MAX;
@ -726,10 +726,6 @@ bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
recurseTreeWithOperation(findRayIntersectionOp, &args);
if (args.found) {
args.distance *= (float)(TREE_SCALE); // scale back up to meters
}
if (gotLock) {
unlock();
}
@ -753,8 +749,7 @@ bool findSpherePenetrationOp(OctreeElement* element, void* extraData) {
SphereArgs* args = static_cast<SphereArgs*>(extraData);
// coarse check against bounds
const AACube& box = element->getAACube();
if (!box.expandedContains(args->center, args->radius)) {
if (!element->getAACube().expandedContains(args->center, args->radius)) {
return false;
}
if (element->hasContent()) {
@ -762,7 +757,7 @@ bool findSpherePenetrationOp(OctreeElement* element, void* extraData) {
if (element->findSpherePenetration(args->center, args->radius, elementPenetration, &args->penetratedObject)) {
// NOTE: it is possible for this penetration accumulation algorithm to produce a
// final penetration vector with zero length.
args->penetration = addPenetrations(args->penetration, elementPenetration * (float)(TREE_SCALE));
args->penetration = addPenetrations(args->penetration, elementPenetration);
args->found = true;
}
}
@ -776,8 +771,8 @@ bool Octree::findSpherePenetration(const glm::vec3& center, float radius, glm::v
void** penetratedObject, Octree::lockType lockType, bool* accurateResult) {
SphereArgs args = {
center / (float)(TREE_SCALE),
radius / (float)(TREE_SCALE),
center,
radius,
penetration,
false,
NULL };
@ -838,14 +833,13 @@ bool findCapsulePenetrationOp(OctreeElement* element, void* extraData) {
CapsuleArgs* args = static_cast<CapsuleArgs*>(extraData);
// coarse check against bounds
const AACube& box = element->getAACube();
if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) {
if (!element->getAACube().expandedIntersectsSegment(args->start, args->end, args->radius)) {
return false;
}
if (element->hasContent()) {
glm::vec3 nodePenetration;
if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) {
args->penetration = addPenetrations(args->penetration, nodePenetration * (float)(TREE_SCALE));
if (element->getAACube().findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) {
args->penetration = addPenetrations(args->penetration, nodePenetration);
args->found = true;
}
}
@ -872,8 +866,7 @@ bool findContentInCubeOp(OctreeElement* element, void* extraData) {
ContentArgs* args = static_cast<ContentArgs*>(extraData);
// coarse check against bounds
AACube cube = element->getAACube();
cube.scale(TREE_SCALE);
const AACube& cube = element->getAACube();
if (!cube.touches(args->cube)) {
return false;
}
@ -892,12 +885,7 @@ bool findContentInCubeOp(OctreeElement* element, void* extraData) {
bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius,
glm::vec3& penetration, Octree::lockType lockType, bool* accurateResult) {
CapsuleArgs args = {
start / (float)(TREE_SCALE),
end / (float)(TREE_SCALE),
radius / (float)(TREE_SCALE),
penetration,
false };
CapsuleArgs args = { start, end, radius, penetration, false };
penetration = glm::vec3(0.0f, 0.0f, 0.0f);
bool gotLock = false;
@ -945,8 +933,7 @@ public:
// Find the smallest colored voxel enclosing a point (if there is one)
bool getElementEnclosingOperation(OctreeElement* element, void* extraData) {
GetElementEnclosingArgs* args = static_cast<GetElementEnclosingArgs*>(extraData);
AACube elementBox = element->getAACube();
if (elementBox.contains(args->point)) {
if (element->getAACube().contains(args->point)) {
if (element->hasContent() && element->isLeaf()) {
// we've reached a solid leaf containing the point, return the element.
args->element = element;
@ -1212,9 +1199,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
// If the user also asked for occlusion culling, check if this element is occluded, but only if it's not a leaf.
// leaf occlusion is handled down below when we check child nodes
if (params.wantOcclusionCulling && !element->isLeaf()) {
AACube voxelBox = element->getAACube();
voxelBox.scale(TREE_SCALE);
OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(voxelBox));
OctreeProjectedPolygon* voxelPolygon =
new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(element->getAACube()));
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion
// culling and proceed as normal
@ -1365,10 +1351,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
if (params.wantOcclusionCulling && childElement->isLeaf()) {
// Don't check occlusion here, just add them to our distance ordered array...
AACube voxelBox = childElement->getAACube();
voxelBox.scale(TREE_SCALE);
OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(
params.viewFrustum->getProjectedPolygon(voxelBox));
params.viewFrustum->getProjectedPolygon(childElement->getAACube()));
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we ignore occlusion
// culling and proceed as normal
@ -2084,58 +2068,6 @@ bool Octree::countOctreeElementsOperation(OctreeElement* element, void* extraDat
return true; // keep going
}
void Octree::copySubTreeIntoNewTree(OctreeElement* startElement, Octree* destinationTree, bool rebaseToRoot) {
OctreeElementBag elementBag;
elementBag.insert(startElement);
int chopLevels = 0;
if (rebaseToRoot) {
chopLevels = numberOfThreeBitSectionsInCode(startElement->getOctalCode());
}
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS, chopLevels);
OctreeElementExtraEncodeData extraEncodeData;
params.extraEncodeData = &extraEncodeData;
OctreePacketData packetData;
while (!elementBag.isEmpty()) {
OctreeElement* subTree = elementBag.extract();
packetData.reset(); // reset the packet between usage
// ask our tree to write a bitsteam
encodeTreeBitstream(subTree, &packetData, elementBag, params);
// ask destination tree to read the bitstream
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS);
destinationTree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
}
}
void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationElement) {
OctreeElementBag elementBag;
// If we were given a specific element, start from there, otherwise start from root
elementBag.insert(sourceTree->_rootElement);
OctreePacketData packetData;
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
OctreeElementExtraEncodeData extraEncodeData;
params.extraEncodeData = &extraEncodeData;
while (!elementBag.isEmpty()) {
OctreeElement* subTree = elementBag.extract();
packetData.reset(); // reset between usage
// ask our tree to write a bitsteam
sourceTree->encodeTreeBitstream(subTree, &packetData, elementBag, params);
// ask destination tree to read the bitstream
bool wantImportProgress = true;
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationElement,
0, SharedNodePointer(), wantImportProgress);
readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
}
}
void Octree::cancelImport() {
_stopImport = true;
}

View file

@ -270,6 +270,9 @@ public:
void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL);
void recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData = NULL);
/// \param operation type of operation
/// \param point point in world-frame (meters)
/// \param extraData hook for user data to be interpreted by special context
void recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation,
const glm::vec3& point, void* extraData = NULL);
@ -308,8 +311,13 @@ public:
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration,
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
/// \param cube query cube in world-frame (meters)
/// \param[out] cubes list of cubes (world-frame) of child elements that have content
bool findContentInCube(const AACube& cube, CubeList& cubes);
/// \param point query point in world-frame (meters)
/// \param lockType how to lock the tree (Lock, TryLock, NoLock)
/// \param[out] accurateResult pointer to output result, will be set "true" or "false" if non-null
OctreeElement* getElementEnclosingPoint(const glm::vec3& point,
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
@ -323,9 +331,6 @@ public:
unsigned long getOctreeElementsCount();
void copySubTreeIntoNewTree(OctreeElement* startElement, Octree* destinationTree, bool rebaseToRoot);
void copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationElement);
bool getShouldReaverage() const { return _shouldReaverage; }
void recurseElementWithOperation(OctreeElement* element, RecurseOctreeOperation operation,

View file

@ -194,14 +194,14 @@ void OctreeElement::setShouldRender(bool shouldRender) {
}
void OctreeElement::calculateAACube() {
glm::vec3 corner;
// copy corner into cube
copyFirstVertexForCode(getOctalCode(),(float*)&corner);
glm::vec3 corner;
copyFirstVertexForCode(getOctalCode(), (float*)&corner);
// this tells you the "size" of the voxel
float voxelScale = 1 / powf(2, numberOfThreeBitSectionsInCode(getOctalCode()));
_cube.setBox(corner,voxelScale);
float voxelScale = (float)TREE_SCALE / powf(2.0f, numberOfThreeBitSectionsInCode(getOctalCode()));
corner *= (float)TREE_SCALE;
_cube.setBox(corner, voxelScale);
}
void OctreeElement::deleteChildAtIndex(int childIndex) {
@ -1221,9 +1221,7 @@ float OctreeElement::getEnclosingRadius() const {
}
ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const {
AACube cube = _cube; // use temporary cube so we can scale it
cube.scale(TREE_SCALE);
return viewFrustum.cubeInFrustum(cube);
return viewFrustum.cubeInFrustum(_cube);
}
// There are two types of nodes for which we want to "render"
@ -1257,14 +1255,13 @@ bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float
// does as much math as possible in voxel scale and then scales up to TREE_SCALE at end
float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const {
glm::vec3 furthestPoint;
viewFrustum.getFurthestPointFromCameraVoxelScale(getAACube(), furthestPoint);
glm::vec3 temp = viewFrustum.getPositionVoxelScale() - furthestPoint;
float distanceToFurthestPoint = sqrtf(glm::dot(temp, temp));
return distanceToFurthestPoint * (float)TREE_SCALE;
viewFrustum.getFurthestPointFromCamera(_cube, furthestPoint);
glm::vec3 temp = viewFrustum.getPosition() - furthestPoint;
return sqrtf(glm::dot(temp, temp));
}
float OctreeElement::distanceToCamera(const ViewFrustum& viewFrustum) const {
glm::vec3 center = _cube.calcCenter() * (float)TREE_SCALE;
glm::vec3 center = _cube.calcCenter();
glm::vec3 temp = viewFrustum.getPosition() - center;
float distanceToVoxelCenter = sqrtf(glm::dot(temp, temp));
return distanceToVoxelCenter;
@ -1337,16 +1334,12 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3
keepSearching = true; // assume that we will continue searching after this.
AACube cube = getAACube();
float distanceToElementCube = std::numeric_limits<float>::max();
float distanceToElementDetails = distance;
BoxFace localFace;
AACube debugCube = cube;
debugCube.scale((float)TREE_SCALE);
// if the ray doesn't intersect with our cube, we can stop searching!
if (!cube.findRayIntersection(origin, direction, distanceToElementCube, localFace)) {
if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace)) {
keepSearching = false; // no point in continuing to search
return false; // we did not intersect
}
@ -1358,7 +1351,7 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3
// if the distance to the element cube is not less than the current best distance, then it's not possible
// for any details inside the cube to be closer so we don't need to consider them.
if (cube.contains(origin) || distanceToElementCube < distance) {
if (_cube.contains(origin) || distanceToElementCube < distance) {
if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails,
face, intersectedObject, precisionPicking, distanceToElementCube)) {
@ -1392,6 +1385,7 @@ bool OctreeElement::findDetailedRayIntersection(const glm::vec3& origin, const g
bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const {
// center and radius are in meters, so we have to scale the _cube into world-frame
return _cube.findSpherePenetration(center, radius, penetration);
}
@ -1529,15 +1523,15 @@ int OctreeElement::getMyChildContaining(const AACube& cube) const {
if (cubeScale > ourScale) {
qDebug() << "UNEXPECTED -- OctreeElement::getMyChildContaining() -- (cubeScale > ourScale)";
qDebug() << " cube=" << cube;
qDebug() << " elements AACube=" << getAACube();
qDebug() << " elements AACube=" << _cube;
qDebug() << " cubeScale=" << cubeScale;
qDebug() << " ourScale=" << ourScale;
assert(false);
}
// Determine which of our children the minimum and maximum corners of the cube live in...
glm::vec3 cubeCornerMinimum = glm::clamp(cube.getCorner(), 0.0f, 1.0f);
glm::vec3 cubeCornerMaximum = glm::clamp(cube.calcTopFarLeft(), 0.0f, 1.0f);
glm::vec3 cubeCornerMinimum = glm::clamp(cube.getCorner(), 0.0f, (float)TREE_SCALE);
glm::vec3 cubeCornerMaximum = glm::clamp(cube.calcTopFarLeft(), 0.0f, (float)TREE_SCALE);
if (_cube.contains(cubeCornerMinimum) && _cube.contains(cubeCornerMaximum)) {
int childIndexCubeMinimum = getMyChildContainingPoint(cubeCornerMinimum);

View file

@ -125,6 +125,10 @@ public:
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking, float distanceToElementCube);
/// \param center center of sphere in meters
/// \param radius radius of sphere in meters
/// \param[out] penetration pointing into cube from sphere
/// \param penetratedObject unused
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const;

View file

@ -101,7 +101,6 @@ void OctreeHeadlessViewer::queryOctree() {
voxelDetailsForCode(rootCode, rootDetails);
jurisdictions.unlock();
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverBounds.scale(TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
@ -170,7 +169,6 @@ void OctreeHeadlessViewer::queryOctree() {
voxelDetailsForCode(rootCode, rootDetails);
jurisdictions.unlock();
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverBounds.scale(TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {

View file

@ -65,7 +65,8 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar
unsigned int numBytesPacketHeader = numBytesForPacketHeader(dataByteArray);
QUuid sourceUUID = uuidFromPacketHeader(dataByteArray);
PacketType expectedType = getExpectedPacketType();
PacketVersion expectedVersion = _tree->expectedVersion(); // TODO: would be better to read this from the packet!
// packetVersion is the second byte
PacketVersion packetVersion = dataByteArray[1];
if(command == expectedType) {
PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram expected PacketType", showTimingDetails);
@ -117,7 +118,7 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar
if (sectionLength) {
// ask the VoxelTree to read the bitstream into the tree
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL,
sourceUUID, sourceNode, false, expectedVersion);
sourceUUID, sourceNode, false, packetVersion);
_tree->lockForWrite();
OctreePacketData packetData(packetIsCompressed);
packetData.loadFinalizedContent(dataAt, sectionLength);

View file

@ -837,33 +837,6 @@ void ViewFrustum::getFurthestPointFromCamera(const AACube& box, glm::vec3& furth
}
}
void ViewFrustum::getFurthestPointFromCameraVoxelScale(const AACube& box, glm::vec3& furthestPoint) const {
const glm::vec3& bottomNearRight = box.getCorner();
float scale = box.getScale();
float halfScale = scale * 0.5f;
if (_positionVoxelScale.x < bottomNearRight.x + halfScale) {
// we are to the right of the center, so the left edge is furthest
furthestPoint.x = bottomNearRight.x + scale;
} else {
furthestPoint.x = bottomNearRight.x;
}
if (_positionVoxelScale.y < bottomNearRight.y + halfScale) {
// we are below of the center, so the top edge is furthest
furthestPoint.y = bottomNearRight.y + scale;
} else {
furthestPoint.y = bottomNearRight.y;
}
if (_positionVoxelScale.z < bottomNearRight.z + halfScale) {
// we are to the near side of the center, so the far side edge is furthest
furthestPoint.z = bottomNearRight.z + scale;
} else {
furthestPoint.z = bottomNearRight.z;
}
}
float ViewFrustum::distanceToCamera(const glm::vec3& point) const {
glm::vec3 temp = getPosition() - point;
float distanceToPoint = sqrtf(glm::dot(temp, temp));

View file

@ -30,19 +30,18 @@ const float DEFAULT_KEYHOLE_RADIUS = 3.0f;
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f;
const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f;
const float DEFAULT_NEAR_CLIP = 0.08f;
const float DEFAULT_FAR_CLIP = TREE_SCALE;
const float DEFAULT_FAR_CLIP = (float)TREE_SCALE;
class ViewFrustum {
public:
ViewFrustum();
// setters for camera attributes
void setPosition(const glm::vec3& p) { _position = p; _positionVoxelScale = (p / (float)TREE_SCALE); }
void setPosition(const glm::vec3& p) { _position = p; }
void setOrientation(const glm::quat& orientationAsQuaternion);
// getters for camera attributes
const glm::vec3& getPosition() const { return _position; }
const glm::vec3& getPositionVoxelScale() const { return _positionVoxelScale; }
const glm::quat& getOrientation() const { return _orientation; }
const glm::vec3& getDirection() const { return _direction; }
const glm::vec3& getUp() const { return _up; }
@ -119,9 +118,6 @@ public:
OctreeProjectedPolygon getProjectedPolygon(const AACube& box) const;
void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const;
// assumes box is in voxel scale, not TREE_SCALE, will scale view frustum's position accordingly
void getFurthestPointFromCameraVoxelScale(const AACube& box, glm::vec3& furthestPoint) const;
float distanceToCamera(const glm::vec3& point) const;
void evalProjectionMatrix(glm::mat4& proj) const;
@ -135,8 +131,7 @@ private:
void calculateOrthographic();
// camera location/orientation attributes
glm::vec3 _position = glm::vec3(0.0f); // the position in TREE_SCALE
glm::vec3 _positionVoxelScale = glm::vec3(0.0f); // the position in voxel scale
glm::vec3 _position = glm::vec3(0.0f); // the position in world-frame
glm::quat _orientation = glm::quat();
// calculated for orientation

View file

@ -84,23 +84,22 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
// bypass const-ness so we can remember the substep
const_cast<EntityMotionState*>(this)->_lastKinematicSubstep = substep;
}
worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset()));
worldTrans.setOrigin(glmToBullet(_entity->getPosition() - ObjectMotionState::getWorldOffset()));
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
}
// This callback is invoked by the physics simulation at the end of each simulation frame...
// iff the corresponding RigidBody is DYNAMIC and has moved.
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
_entity->setPositionInMeters(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset());
_entity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset());
_entity->setRotation(bulletToGLM(worldTrans.getRotation()));
glm::vec3 v;
getVelocity(v);
_entity->setVelocityInMeters(v);
_entity->setVelocity(v);
getAngularVelocity(v);
// DANGER! EntityItem stores angularVelocity in degrees/sec!!!
_entity->setAngularVelocity(glm::degrees(v));
_entity->setAngularVelocity(v);
_entity->setLastSimulated(usecTimestampNow());
@ -119,7 +118,7 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) {
if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
if (flags & EntityItem::DIRTY_POSITION) {
_sentPosition = _entity->getPositionInMeters() - ObjectMotionState::getWorldOffset();
_sentPosition = _entity->getPosition() - ObjectMotionState::getWorldOffset();
btTransform worldTrans;
worldTrans.setOrigin(glmToBullet(_sentPosition));
@ -156,14 +155,13 @@ void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) {
void EntityMotionState::updateObjectVelocities() {
if (_body) {
_sentVelocity = _entity->getVelocityInMeters();
_sentVelocity = _entity->getVelocity();
setVelocity(_sentVelocity);
// DANGER! EntityItem stores angularVelocity in degrees/sec!!!
_sentAngularVelocity = glm::radians(_entity->getAngularVelocity());
_sentAngularVelocity = _entity->getAngularVelocity();
setAngularVelocity(_sentAngularVelocity);
_sentAcceleration = _entity->getGravityInMeters();
_sentAcceleration = _entity->getGravity();
setGravity(_sentAcceleration);
_body->setActivationState(ACTIVE_TAG);
@ -219,8 +217,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.setVelocity(_sentVelocity);
_sentAcceleration = bulletToGLM(_body->getGravity());
properties.setGravity(_sentAcceleration);
// DANGER! EntityItem stores angularVelocity in degrees/sec!!!
properties.setAngularVelocity(glm::degrees(_sentAngularVelocity));
properties.setAngularVelocity(_sentAngularVelocity);
}
// RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit.

View file

@ -13,6 +13,7 @@
#include "ShapeInfoUtil.h"
#include "PhysicsHelpers.h"
#include "ThreadSafeDynamicsWorld.h"
#include "AvatarData.h"
static uint32_t _numSubsteps;
@ -286,6 +287,20 @@ void PhysicsEngine::stepSimulation() {
_clock.reset();
float timeStep = btMin(dt, MAX_TIMESTEP);
if (_avatarData->isPhysicsEnabled()) {
_avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
glmToBullet(_avatarData->getPosition())));
// WORKAROUND: there is a bug in the debug Bullet-2.82 libs where a zero length walk velocity will trigger
// an assert when the getNormalizedVector() helper function in btKinematicCharacterController.cpp tries to
// first normalize a vector before checking its length. Here we workaround the problem by checking the
// length first. NOTE: the character's velocity is reset to zero after each step, so when we DON'T set
// the velocity for this time interval it is the same thing as setting its velocity to zero.
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
if (walkVelocity.length2() > FLT_EPSILON * FLT_EPSILON) {
_characterController->setVelocityForTimeInterval(walkVelocity, timeStep);
}
}
// This is step (2).
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
_numSubsteps += (uint32_t)numSubsteps;
@ -302,9 +317,18 @@ void PhysicsEngine::stepSimulation() {
//
// TODO: untangle these lock sequences.
_entityTree->lockForWrite();
_avatarData->lockForWrite();
lock();
_dynamicsWorld->synchronizeMotionStates();
if (_avatarData->isPhysicsEnabled()) {
const btTransform& avatarTransform = _avatarGhostObject->getWorldTransform();
_avatarData->setOrientation(bulletToGLM(avatarTransform.getRotation()));
_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()));
}
unlock();
_avatarData->unlock();
_entityTree->unlock();
computeCollisionEvents();
@ -582,3 +606,32 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
body->activate();
return true;
}
void PhysicsEngine::setAvatarData(AvatarData *avatarData) {
_avatarData = avatarData;
_avatarGhostObject = new btPairCachingGhostObject();
_avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
glmToBullet(_avatarData->getPosition())));
// XXX these values should be computed from the character model.
btScalar characterRadius = 0.3;
btScalar characterHeight = 1.75 - 2.0f * characterRadius;
btScalar stepHeight = btScalar(0.35);
btConvexShape* capsule = new btCapsuleShape(characterRadius, characterHeight);
_avatarGhostObject->setCollisionShape(capsule);
_avatarGhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
_characterController = new btKinematicCharacterController(_avatarGhostObject, capsule, stepHeight);
_dynamicsWorld->addCollisionObject(_avatarGhostObject, btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
_dynamicsWorld->addAction(_characterController);
_characterController->reset (_dynamicsWorld);
// _characterController->warp (btVector3(10.210001,-2.0306311,16.576973));
btGhostPairCallback* ghostPairCallback = new btGhostPairCallback();
_dynamicsWorld->getPairCache()->setInternalGhostPairCallback(ghostPairCallback);
}

View file

@ -16,6 +16,11 @@
#include <QSet>
#include <btBulletDynamicsCommon.h>
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
#include <BulletCollision/CollisionShapes/btCapsuleShape.h>
#include <BulletDynamics/Character/btKinematicCharacterController.h>
#include <EntityItem.h>
#include <EntitySimulation.h>
@ -25,6 +30,7 @@
#include "EntityMotionState.h"
#include "ShapeManager.h"
#include "ThreadSafeDynamicsWorld.h"
#include "AvatarData.h"
const float HALF_SIMULATION_EXTENT = 512.0f; // meters
@ -82,6 +88,8 @@ public:
/// process queue of changed from external sources
void relayIncomingChangesToSimulation();
void setAvatarData(AvatarData *avatarData);
private:
/// \param motionState pointer to Object's MotionState
void removeObjectFromBullet(ObjectMotionState* motionState);
@ -113,6 +121,11 @@ private:
ContactMap _contactMap;
uint32_t _numContactFrames = 0;
uint32_t _lastNumSubstepsAtUpdateInternal = 0;
/// character collisions
btCharacterControllerInterface* _characterController = 0;
class btPairCachingGhostObject* _avatarGhostObject = 0;
AvatarData *_avatarData = 0;
};
#endif // hifi_PhysicsEngine_h

View file

@ -61,6 +61,8 @@ public:
bool getShapeCollisions(const Shape* shape, CollisionList& collisions) const;
void setupAvatarCollision();
protected:
void integrate(float deltaTime);

View file

@ -2084,6 +2084,7 @@ void GeometryReader::run() {
urlValid &= !urlname.isEmpty();
urlValid &= !_url.path().isEmpty();
urlValid &= _url.path().toLower().endsWith(".fbx")
|| _url.path().toLower().endsWith(".obj")
|| _url.path().toLower().endsWith(".svo");
if (urlValid) {
@ -2101,6 +2102,8 @@ void GeometryReader::run() {
lightmapLevel = 3.5f;
}
fbxgeo = readFBX(_reply, _mapping, grabLightmaps, lightmapLevel);
} else if (_url.path().toLower().endsWith(".obj")) {
fbxgeo = readOBJ(_reply, _mapping);
}
QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo));
} else {

Some files were not shown because too many files have changed in this diff Show more