mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 11:35:20 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into 20397
This commit is contained in:
commit
d0c4dddc1b
32 changed files with 488 additions and 153 deletions
|
@ -74,6 +74,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "maximum_user_capacity",
|
||||
"label": "Maximum User Capacity",
|
||||
"help": "The limit on how many avatars can be connected at once. 0 means no limit.",
|
||||
"placeholder": "0",
|
||||
"default": "0",
|
||||
"advanced": false
|
||||
},
|
||||
{
|
||||
"name": "allowed_editors",
|
||||
"type": "table",
|
||||
|
|
|
@ -44,6 +44,7 @@ const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
|
|||
|
||||
|
||||
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
||||
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
||||
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
|
||||
|
||||
|
||||
|
@ -667,9 +668,22 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
|||
}
|
||||
|
||||
|
||||
unsigned int DomainServer::countConnectedUsers() {
|
||||
unsigned int result = 0;
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
nodeList->eachNode([&](const SharedNodePointer& otherNode){
|
||||
if (otherNode->getType() == NodeType::Agent) {
|
||||
result++;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
||||
const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
|
||||
ALLOWED_USERS_SETTINGS_KEYPATH);
|
||||
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
||||
|
@ -679,6 +693,18 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
|||
|| senderSockAddr.getAddress() == QHostAddress::LocalHost) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
||||
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
||||
if (maximumUserCapacity > 0) {
|
||||
unsigned int connectedUsers = countConnectedUsers();
|
||||
if (connectedUsers >= maximumUserCapacity) {
|
||||
// too many users, deny the new connection.
|
||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
||||
return false;
|
||||
}
|
||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
|
||||
}
|
||||
|
||||
if (allowedUsers.count() > 0) {
|
||||
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
|
||||
|
|
|
@ -83,6 +83,7 @@ private:
|
|||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
unsigned int countConnectedUsers();
|
||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
|
|
@ -18,3 +18,4 @@ Script.load("lobby.js");
|
|||
Script.load("notifications.js");
|
||||
Script.load("look.js");
|
||||
Script.load("users.js");
|
||||
Script.load("utilities/LODWarning.js");
|
||||
|
|
16
examples/example/ui/LODManagerExample.js
Normal file
16
examples/example/ui/LODManagerExample.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
LODManager.LODIncreased.connect(function() {
|
||||
print("LOD has been increased. You can now see "
|
||||
+ LODManager.getLODFeedbackText()
|
||||
+ ", fps:" + LODManager.getFPSAverage()
|
||||
+ ", fast fps:" + LODManager.getFastFPSAverage()
|
||||
);
|
||||
});
|
||||
|
||||
LODManager.LODDecreased.connect(function() {
|
||||
print("LOD has been decreased. You can now see "
|
||||
+ LODManager.getLODFeedbackText()
|
||||
+ ", fps:" + LODManager.getFPSAverage()
|
||||
+ ", fast fps:" + LODManager.getFastFPSAverage()
|
||||
);
|
||||
});
|
|
@ -13,4 +13,5 @@ Script.load("progress.js");
|
|||
Script.load("lobby.js");
|
||||
Script.load("notifications.js");
|
||||
Script.load("controllers/oculus/goTo.js");
|
||||
Script.load("utilities/LODWarning.js");
|
||||
//Script.load("scripts.js"); // Not created yet
|
||||
|
|
115
examples/utilities/LODWarning.js
Normal file
115
examples/utilities/LODWarning.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
// LODWarning.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 3/17/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script will display a warning when the LOD is adjusted to do scene complexity.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var DISPLAY_WARNING_FOR = 3; // in seconds
|
||||
var DISTANCE_FROM_CAMERA = 2;
|
||||
var SHOW_LOD_UP_MESSAGE = false; // By default we only display the LOD message when reducing LOD
|
||||
|
||||
|
||||
var warningIsVisible = false; // initially the warning is hidden
|
||||
var warningShownAt = 0;
|
||||
var billboardPosition = Vec3.sum(Camera.getPosition(),
|
||||
Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var warningOverlay = Overlays.addOverlay("text3d", {
|
||||
position: billboardPosition,
|
||||
dimensions: { x: 2, y: 1.25 },
|
||||
width: 2,
|
||||
height: 1.25,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
topMargin: 0.1,
|
||||
leftMargin: 0.1,
|
||||
lineHeight: 0.07,
|
||||
text: "",
|
||||
alpha: 0.5,
|
||||
backgroundAlpha: 0.7,
|
||||
isFacingAvatar: true,
|
||||
visible: warningIsVisible,
|
||||
});
|
||||
|
||||
// Handle moving the billboard to remain in front of the camera
|
||||
var billboardNeedsMoving = false;
|
||||
Script.update.connect(function() {
|
||||
|
||||
if (warningIsVisible) {
|
||||
var bestBillboardPosition = Vec3.sum(Camera.getPosition(),
|
||||
Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var MAX_DISTANCE = 0.5;
|
||||
var CLOSE_ENOUGH = 0.01;
|
||||
if (!billboardNeedsMoving && Vec3.distance(bestBillboardPosition, billboardPosition) > MAX_DISTANCE) {
|
||||
billboardNeedsMoving = true;
|
||||
}
|
||||
|
||||
if (billboardNeedsMoving && Vec3.distance(bestBillboardPosition, billboardPosition) <= CLOSE_ENOUGH) {
|
||||
billboardNeedsMoving = false;
|
||||
}
|
||||
|
||||
if (billboardNeedsMoving) {
|
||||
// slurp the billboard to the best location
|
||||
moveVector = Vec3.multiply(0.05, Vec3.subtract(bestBillboardPosition, billboardPosition));
|
||||
billboardPosition = Vec3.sum(billboardPosition, moveVector);
|
||||
Overlays.editOverlay(warningOverlay, { position: billboardPosition });
|
||||
}
|
||||
|
||||
var now = new Date();
|
||||
var sinceWarningShown = now - warningShownAt;
|
||||
if (sinceWarningShown > 1000 * DISPLAY_WARNING_FOR) {
|
||||
warningIsVisible = false;
|
||||
Overlays.editOverlay(warningOverlay, { visible: warningIsVisible });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LODManager.LODIncreased.connect(function() {
|
||||
if (SHOW_LOD_UP_MESSAGE) {
|
||||
// if the warning wasn't visible, then move it before showing it.
|
||||
if (!warningIsVisible) {
|
||||
billboardPosition = Vec3.sum(Camera.getPosition(),
|
||||
Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation())));
|
||||
Overlays.editOverlay(warningOverlay, { position: billboardPosition });
|
||||
}
|
||||
|
||||
warningShownAt = new Date();
|
||||
warningIsVisible = true;
|
||||
warningText = "Level of detail has been increased. \n"
|
||||
+ "You can now see: \n"
|
||||
+ LODManager.getLODFeedbackText();
|
||||
|
||||
Overlays.editOverlay(warningOverlay, { visible: warningIsVisible, text: warningText });
|
||||
}
|
||||
});
|
||||
|
||||
LODManager.LODDecreased.connect(function() {
|
||||
// if the warning wasn't visible, then move it before showing it.
|
||||
if (!warningIsVisible) {
|
||||
billboardPosition = Vec3.sum(Camera.getPosition(),
|
||||
Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation())));
|
||||
Overlays.editOverlay(warningOverlay, { position: billboardPosition });
|
||||
}
|
||||
|
||||
warningShownAt = new Date();
|
||||
warningIsVisible = true;
|
||||
warningText = "\n"
|
||||
+ "Due to the complexity of the content, the \n"
|
||||
+ "level of detail has been decreased. \n"
|
||||
+ "You can now see: \n"
|
||||
+ LODManager.getLODFeedbackText();
|
||||
|
||||
Overlays.editOverlay(warningOverlay, { visible: warningIsVisible, text: warningText });
|
||||
});
|
||||
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
Overlays.deleteOverlay(warningOverlay);
|
||||
});
|
|
@ -891,9 +891,10 @@ bool Application::event(QEvent* event) {
|
|||
DependencyManager::get<AddressManager>()->handleLookupString(fileEvent->url().toString());
|
||||
} else if (url.path().toLower().endsWith(SVO_EXTENSION)) {
|
||||
emit svoImportRequested(url.url());
|
||||
} else if (url.path().toLower().endsWith(JS_EXTENSION)) {
|
||||
askToLoadScript(url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1454,31 +1455,43 @@ void Application::wheelEvent(QWheelEvent* event) {
|
|||
void Application::dropEvent(QDropEvent *event) {
|
||||
QString snapshotPath;
|
||||
const QMimeData *mimeData = event->mimeData();
|
||||
bool atLeastOneFileAccepted = false;
|
||||
foreach (QUrl url, mimeData->urls()) {
|
||||
auto lower = url.path().toLower();
|
||||
if (lower.endsWith(SNAPSHOT_EXTENSION)) {
|
||||
snapshotPath = url.toLocalFile();
|
||||
break;
|
||||
|
||||
|
||||
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
|
||||
if (snapshotData) {
|
||||
if (!snapshotData->getDomain().isEmpty()) {
|
||||
DependencyManager::get<NodeList>()->getDomainHandler().setHostnameAndPort(snapshotData->getDomain());
|
||||
}
|
||||
|
||||
_myAvatar->setPosition(snapshotData->getLocation());
|
||||
_myAvatar->setOrientation(snapshotData->getOrientation());
|
||||
atLeastOneFileAccepted = true;
|
||||
break; // don't process further files
|
||||
} else {
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("No location details were found in the file "
|
||||
+ snapshotPath + ", try dragging in an authentic Hifi snapshot.");
|
||||
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.exec();
|
||||
}
|
||||
} else if (lower.endsWith(SVO_EXTENSION)) {
|
||||
emit svoImportRequested(url.url());
|
||||
event->acceptProposedAction();
|
||||
return;
|
||||
atLeastOneFileAccepted = true;
|
||||
} else if (lower.endsWith(JS_EXTENSION)) {
|
||||
askToLoadScript(url.url());
|
||||
atLeastOneFileAccepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
|
||||
if (snapshotData) {
|
||||
if (!snapshotData->getDomain().isEmpty()) {
|
||||
DependencyManager::get<NodeList>()->getDomainHandler().setHostnameAndPort(snapshotData->getDomain());
|
||||
}
|
||||
|
||||
_myAvatar->setPosition(snapshotData->getLocation());
|
||||
_myAvatar->setOrientation(snapshotData->getOrientation());
|
||||
} else {
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("No location details were found in this JPG, try dragging in an authentic Hifi snapshot.");
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.exec();
|
||||
|
||||
if (atLeastOneFileAccepted) {
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2799,18 +2812,22 @@ QImage Application::renderAvatarBillboard() {
|
|||
static QThread * activeRenderingThread = nullptr;
|
||||
|
||||
ViewFrustum* Application::getViewFrustum() {
|
||||
#ifdef DEBUG
|
||||
if (QThread::currentThread() == activeRenderingThread) {
|
||||
// FIXME, should this be an assert?
|
||||
qWarning() << "Calling Application::getViewFrustum() from the active rendering thread, did you mean Application::getDisplayViewFrustum()?";
|
||||
}
|
||||
#endif
|
||||
return &_viewFrustum;
|
||||
}
|
||||
|
||||
ViewFrustum* Application::getDisplayViewFrustum() {
|
||||
#ifdef DEBUG
|
||||
if (QThread::currentThread() != activeRenderingThread) {
|
||||
// FIXME, should this be an assert?
|
||||
qWarning() << "Calling Application::getDisplayViewFrustum() from outside the active rendering thread or outside rendering, did you mean Application::getViewFrustum()?";
|
||||
}
|
||||
#endif
|
||||
return &_displayViewFrustum;
|
||||
}
|
||||
|
||||
|
@ -3548,6 +3565,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
|
||||
scriptEngine->registerGlobalObject("UndoStack", &_undoStackScriptingInterface);
|
||||
|
||||
scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data());
|
||||
|
||||
QScriptValue hmdInterface = scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance());
|
||||
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
|
||||
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
||||
|
@ -3579,6 +3598,19 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
workerThread->start();
|
||||
}
|
||||
|
||||
void Application::askToLoadScript(const QString& scriptFilenameOrURL) {
|
||||
QMessageBox::StandardButton reply;
|
||||
QString message = "Would you like to run this script:\n" + scriptFilenameOrURL;
|
||||
reply = QMessageBox::question(getWindow(), "Run Script", message, QMessageBox::Yes|QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
qDebug() << "Chose to run the script: " << scriptFilenameOrURL;
|
||||
loadScript(scriptFilenameOrURL);
|
||||
} else {
|
||||
qDebug() << "Declined to run the script: " << scriptFilenameOrURL;
|
||||
}
|
||||
}
|
||||
|
||||
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
|
||||
bool loadScriptFromEditor, bool activateMainWindow) {
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ static const float NODE_KILLED_BLUE = 0.0f;
|
|||
|
||||
static const QString SNAPSHOT_EXTENSION = ".jpg";
|
||||
static const QString SVO_EXTENSION = ".svo";
|
||||
static const QString JS_EXTENSION = ".js";
|
||||
|
||||
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
|
||||
static const float BILLBOARD_DISTANCE = 5.56f; // meters
|
||||
|
@ -340,6 +341,7 @@ public slots:
|
|||
void loadDialog();
|
||||
void loadScriptURLDialog();
|
||||
void toggleLogDialog();
|
||||
void askToLoadScript(const QString& scriptFilenameOrURL);
|
||||
ScriptEngine* loadScript(const QString& scriptFilename = QString(), bool isUserLoaded = true,
|
||||
bool loadScriptFromEditor = false, bool activateMainWindow = false);
|
||||
void scriptFinished(const QString& scriptName);
|
||||
|
|
|
@ -168,10 +168,10 @@ void GLCanvas::wheelEvent(QWheelEvent* event) {
|
|||
}
|
||||
|
||||
void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
|
||||
const QMimeData *mimeData = event->mimeData();
|
||||
const QMimeData* mimeData = event->mimeData();
|
||||
foreach (QUrl url, mimeData->urls()) {
|
||||
auto lower = url.path().toLower();
|
||||
if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION)) {
|
||||
if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION) || lower.endsWith(JS_EXTENSION)) {
|
||||
event->acceptProposedAction();
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,9 @@ void LODManager::autoAdjustLOD(float currentFPS) {
|
|||
changed = true;
|
||||
_lastAdjust = now;
|
||||
qDebug() << "adjusting LOD down... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
|
||||
<< "_octreeSizeScale=" << _octreeSizeScale;
|
||||
<< "_octreeSizeScale=" << _octreeSizeScale;
|
||||
|
||||
emit LODDecreased();
|
||||
}
|
||||
|
||||
if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS
|
||||
|
@ -87,7 +89,9 @@ void LODManager::autoAdjustLOD(float currentFPS) {
|
|||
changed = true;
|
||||
_lastAdjust = now;
|
||||
qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
|
||||
<< "_octreeSizeScale=" << _octreeSizeScale;
|
||||
<< "_octreeSizeScale=" << _octreeSizeScale;
|
||||
|
||||
emit LODIncreased();
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
|
|
|
@ -21,13 +21,16 @@ const float ADJUST_LOD_DOWN_FPS = 40.0;
|
|||
const float ADJUST_LOD_UP_FPS = 55.0;
|
||||
const float DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f;
|
||||
|
||||
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5;
|
||||
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 0.5; // Consider adjusting LOD down after half a second
|
||||
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
|
||||
|
||||
const float ADJUST_LOD_DOWN_BY = 0.9f;
|
||||
const float ADJUST_LOD_UP_BY = 1.1f;
|
||||
|
||||
const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.25f;
|
||||
// This controls how low the auto-adjust LOD will go a value of 1 means it will adjust to a point where you must be 0.25
|
||||
// meters away from an object of TREE_SCALE before you can see it (which is effectively completely blind). The default value
|
||||
// DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision).
|
||||
const float ADJUST_LOD_MIN_SIZE_SCALE = 1.0f;
|
||||
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
|
||||
|
||||
const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f;
|
||||
|
@ -38,7 +41,8 @@ const int ONE_SECOND_OF_FRAMES = 60;
|
|||
const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES;
|
||||
|
||||
|
||||
class LODManager : public Dependency {
|
||||
class LODManager : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
|
@ -52,21 +56,27 @@ public:
|
|||
float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; }
|
||||
|
||||
// User Tweakable LOD Items
|
||||
QString getLODFeedbackText();
|
||||
void setOctreeSizeScale(float sizeScale);
|
||||
float getOctreeSizeScale() const { return _octreeSizeScale; }
|
||||
Q_INVOKABLE QString getLODFeedbackText();
|
||||
Q_INVOKABLE void setOctreeSizeScale(float sizeScale);
|
||||
Q_INVOKABLE float getOctreeSizeScale() const { return _octreeSizeScale; }
|
||||
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust);
|
||||
Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
|
||||
void autoAdjustLOD(float currentFPS);
|
||||
void resetLODAdjust();
|
||||
Q_INVOKABLE void resetLODAdjust();
|
||||
Q_INVOKABLE float getFPSAverage() const { return _fpsAverage.getAverage(); }
|
||||
Q_INVOKABLE float getFastFPSAverage() const { return _fastFPSAverage.getAverage(); }
|
||||
|
||||
bool shouldRenderMesh(float largestDimension, float distanceToCamera);
|
||||
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
signals:
|
||||
void LODIncreased();
|
||||
void LODDecreased();
|
||||
|
||||
private:
|
||||
LODManager() {}
|
||||
|
||||
|
|
|
@ -37,6 +37,9 @@ bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkReques
|
|||
if (request.url().path().toLower().endsWith(SVO_EXTENSION)) {
|
||||
Application::getInstance()->importSVOFromURL(request.url());
|
||||
return false;
|
||||
} else if (request.url().path().toLower().endsWith(JS_EXTENSION)) {
|
||||
Application::getInstance()->askToLoadScript(request.url().toString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
@ -266,4 +266,53 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
|
|||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
bool RenderableModelEntityItem::isReadyToComputeShape() {
|
||||
if (_collisionModelURL == "") {
|
||||
// no model url, so we're ready to compute a shape.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! _collisionNetworkGeometry.isNull() && _collisionNetworkGeometry->isLoadedWithTextures()) {
|
||||
// we have a _collisionModelURL AND a _collisionNetworkGeometry AND it's fully loaded.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_collisionNetworkGeometry.isNull()) {
|
||||
// we have a _collisionModelURL but we don't yet have a _collisionNetworkGeometry.
|
||||
_collisionNetworkGeometry =
|
||||
DependencyManager::get<GeometryCache>()->getGeometry(_collisionModelURL, QUrl(), false, false);
|
||||
|
||||
if (! _collisionNetworkGeometry.isNull() && _collisionNetworkGeometry->isLoadedWithTextures()) {
|
||||
// shortcut in case it's already loaded.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// the model is still being downloaded.
|
||||
return false;
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||
if (_collisionModelURL == "") {
|
||||
info.setParams(getShapeType(), 0.5f * getDimensions());
|
||||
} else {
|
||||
const FBXGeometry& fbxGeometry = _collisionNetworkGeometry->getFBXGeometry();
|
||||
|
||||
_points.clear();
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
_points << mesh.vertices;
|
||||
}
|
||||
|
||||
info.setParams(getShapeType(), 0.5f * getDimensions(), _collisionModelURL);
|
||||
info.setConvexHull(_points);
|
||||
}
|
||||
}
|
||||
|
||||
ShapeType RenderableModelEntityItem::getShapeType() const {
|
||||
// XXX make hull an option in edit.js ?
|
||||
if (_collisionModelURL != "") {
|
||||
return SHAPE_TYPE_CONVEX_HULL;
|
||||
} else {
|
||||
return _shapeType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ public:
|
|||
_needsInitialSimulation(true),
|
||||
_needsModelReload(true),
|
||||
_myRenderer(NULL),
|
||||
_originalTexturesRead(false) { }
|
||||
_originalTexturesRead(false),
|
||||
_collisionNetworkGeometry(QSharedPointer<NetworkGeometry>()) { }
|
||||
|
||||
virtual ~RenderableModelEntityItem();
|
||||
|
||||
|
@ -52,6 +53,10 @@ public:
|
|||
|
||||
bool needsToCallUpdate() const;
|
||||
|
||||
bool isReadyToComputeShape();
|
||||
void computeShapeInfo(ShapeInfo& info);
|
||||
ShapeType getShapeType() const;
|
||||
|
||||
private:
|
||||
void remapTextures();
|
||||
|
||||
|
@ -62,6 +67,9 @@ private:
|
|||
QString _currentTextures;
|
||||
QStringList _originalTextures;
|
||||
bool _originalTexturesRead;
|
||||
|
||||
QSharedPointer<NetworkGeometry> _collisionNetworkGeometry;
|
||||
QVector<glm::vec3> _points;
|
||||
};
|
||||
|
||||
#endif // hifi_RenderableModelEntityItem_h
|
||||
|
|
|
@ -1002,7 +1002,7 @@ float EntityItem::getRadius() const {
|
|||
return 0.5f * glm::length(_dimensions);
|
||||
}
|
||||
|
||||
void EntityItem::computeShapeInfo(ShapeInfo& info) const {
|
||||
void EntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||
info.setParams(getShapeType(), 0.5f * getDimensions());
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ class EntityTreeElementExtraEncodeData;
|
|||
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
|
||||
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
|
||||
/// one directly, instead you must only construct one of it's derived classes with additional features.
|
||||
class EntityItem {
|
||||
class EntityItem {
|
||||
friend class EntityTreeElement;
|
||||
public:
|
||||
enum EntityDirtyFlags {
|
||||
|
@ -256,7 +256,9 @@ public:
|
|||
|
||||
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;
|
||||
|
||||
virtual bool isReadyToComputeShape() { return true; }
|
||||
virtual void computeShapeInfo(ShapeInfo& info);
|
||||
|
||||
/// return preferred shape type (actual physical shape may differ)
|
||||
virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; }
|
||||
|
@ -295,7 +297,6 @@ public:
|
|||
static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; }
|
||||
static bool getSendPhysicsUpdates() { return _sendPhysicsUpdates; }
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
static bool _sendPhysicsUpdates;
|
||||
|
|
|
@ -176,7 +176,8 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) {
|
|||
_lastEdited = usecTime > _created ? usecTime : _created;
|
||||
}
|
||||
|
||||
const char* shapeTypeNames[] = {"none", "box", "sphere"};
|
||||
const char* shapeTypeNames[] = {"none", "box", "sphere", "ellipsoid", "convex-hull", "plane", "compound", "capsule-x",
|
||||
"capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"};
|
||||
|
||||
QHash<QString, ShapeType> stringToShapeTypeLookup;
|
||||
|
||||
|
@ -184,6 +185,16 @@ void buildStringToShapeTypeLookup() {
|
|||
stringToShapeTypeLookup["none"] = SHAPE_TYPE_NONE;
|
||||
stringToShapeTypeLookup["box"] = SHAPE_TYPE_BOX;
|
||||
stringToShapeTypeLookup["sphere"] = SHAPE_TYPE_SPHERE;
|
||||
stringToShapeTypeLookup["ellipsoid"] = SHAPE_TYPE_ELLIPSOID;
|
||||
stringToShapeTypeLookup["convex-hull"] = SHAPE_TYPE_CONVEX_HULL;
|
||||
stringToShapeTypeLookup["plane"] = SHAPE_TYPE_PLANE;
|
||||
stringToShapeTypeLookup["compound"] = SHAPE_TYPE_COMPOUND;
|
||||
stringToShapeTypeLookup["capsule-x"] = SHAPE_TYPE_CAPSULE_X;
|
||||
stringToShapeTypeLookup["capsule-y"] = SHAPE_TYPE_CAPSULE_Y;
|
||||
stringToShapeTypeLookup["capsule-z"] = SHAPE_TYPE_CAPSULE_Z;
|
||||
stringToShapeTypeLookup["cylinder-x"] = SHAPE_TYPE_CYLINDER_X;
|
||||
stringToShapeTypeLookup["cylinder-y"] = SHAPE_TYPE_CYLINDER_Y;
|
||||
stringToShapeTypeLookup["cylinder-z"] = SHAPE_TYPE_CYLINDER_Z;
|
||||
}
|
||||
|
||||
QString EntityItemProperties::getShapeTypeAsString() const {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "EntityTree.h"
|
||||
#include "EntityTreeElement.h"
|
||||
#include "ModelEntityItem.h"
|
||||
#include "ResourceCache.h"
|
||||
|
||||
const QString ModelEntityItem::DEFAULT_MODEL_URL = QString("");
|
||||
const QString ModelEntityItem::DEFAULT_COLLISION_MODEL_URL = QString("");
|
||||
|
|
|
@ -48,14 +48,26 @@ void ResourceCache::refresh(const QUrl& url) {
|
|||
}
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) {
|
||||
|
||||
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback,
|
||||
bool delayLoad, void* extra, bool block) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QSharedPointer<Resource> result;
|
||||
QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QSharedPointer<Resource>, result), Q_ARG(const QUrl&, url), Q_ARG(const QUrl&, fallback),
|
||||
Q_ARG(bool, delayLoad), Q_ARG(void*, extra));
|
||||
return result;
|
||||
// This will re-call this method in the main thread. If block is true and the main thread
|
||||
// is waiting on a lock, we'll deadlock here.
|
||||
if (block) {
|
||||
QSharedPointer<Resource> result;
|
||||
QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QSharedPointer<Resource>, result), Q_ARG(const QUrl&, url),
|
||||
Q_ARG(const QUrl&, fallback), Q_ARG(bool, delayLoad), Q_ARG(void*, extra));
|
||||
return result;
|
||||
} else {
|
||||
// Queue the re-invocation of this method, but if the main thread is blocked, don't wait. The
|
||||
// return value may be NULL -- it's expected that this will be called again later, in order
|
||||
// to receive the actual Resource.
|
||||
QMetaObject::invokeMethod(this, "getResource", Qt::QueuedConnection,
|
||||
Q_ARG(const QUrl&, url),
|
||||
Q_ARG(const QUrl&, fallback), Q_ARG(bool, delayLoad), Q_ARG(void*, extra));
|
||||
return _resources.value(url);
|
||||
}
|
||||
}
|
||||
|
||||
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) {
|
||||
|
|
|
@ -89,7 +89,7 @@ protected:
|
|||
/// \param delayLoad if true, don't load the resource immediately; wait until load is first requested
|
||||
/// \param extra extra data to pass to the creator, if appropriate
|
||||
Q_INVOKABLE QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl(),
|
||||
bool delayLoad = false, void* extra = NULL);
|
||||
bool delayLoad = false, void* extra = NULL, bool block = true);
|
||||
|
||||
/// Creates a new resource.
|
||||
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
||||
|
|
|
@ -21,6 +21,8 @@ const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of t
|
|||
|
||||
// This controls the LOD. Larger number will make smaller voxels visible at greater distance.
|
||||
const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f;
|
||||
|
||||
// This is used in the LOD Tools to translate between the size scale slider and the values used to set the OctreeSizeScale
|
||||
const float MAX_LOD_SIZE_MULTIPLIER = 2000.0f;
|
||||
|
||||
const int NUMBER_OF_CHILDREN = 8;
|
||||
|
|
|
@ -63,23 +63,25 @@ void PhysicsEngine::addEntityInternal(EntityItem* entity) {
|
|||
assert(entity);
|
||||
void* physicsInfo = entity->getPhysicsInfo();
|
||||
if (!physicsInfo) {
|
||||
ShapeInfo shapeInfo;
|
||||
entity->computeShapeInfo(shapeInfo);
|
||||
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
|
||||
if (shape) {
|
||||
EntityMotionState* motionState = new EntityMotionState(entity);
|
||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||
_entityMotionStates.insert(motionState);
|
||||
addObject(shapeInfo, shape, motionState);
|
||||
} else if (entity->isMoving()) {
|
||||
EntityMotionState* motionState = new EntityMotionState(entity);
|
||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||
_entityMotionStates.insert(motionState);
|
||||
if (entity->isReadyToComputeShape()) {
|
||||
ShapeInfo shapeInfo;
|
||||
entity->computeShapeInfo(shapeInfo);
|
||||
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
|
||||
if (shape) {
|
||||
EntityMotionState* motionState = new EntityMotionState(entity);
|
||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||
_entityMotionStates.insert(motionState);
|
||||
addObject(shapeInfo, shape, motionState);
|
||||
} else if (entity->isMoving()) {
|
||||
EntityMotionState* motionState = new EntityMotionState(entity);
|
||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||
_entityMotionStates.insert(motionState);
|
||||
|
||||
motionState->setKinematic(true, _numSubsteps);
|
||||
_nonPhysicalKinematicObjects.insert(motionState);
|
||||
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
|
||||
//qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
|
||||
motionState->setKinematic(true, _numSubsteps);
|
||||
_nonPhysicalKinematicObjects.insert(motionState);
|
||||
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
|
||||
//qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -322,20 +324,24 @@ void PhysicsEngine::stepSimulation() {
|
|||
//
|
||||
// TODO: untangle these lock sequences.
|
||||
_entityTree->lockForWrite();
|
||||
_avatarData->lockForWrite();
|
||||
|
||||
lock();
|
||||
_dynamicsWorld->synchronizeMotionStates();
|
||||
|
||||
if (_avatarData->isPhysicsEnabled()) {
|
||||
_avatarData->lockForRead();
|
||||
bool avatarHasPhysicsEnabled = _avatarData->isPhysicsEnabled();
|
||||
_avatarData->unlock();
|
||||
if (avatarHasPhysicsEnabled) {
|
||||
const btTransform& avatarTransform = _avatarGhostObject->getWorldTransform();
|
||||
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
|
||||
glm::vec3 offset = rotation * _avatarShapeLocalOffset;
|
||||
_avatarData->lockForWrite();
|
||||
_avatarData->setOrientation(rotation);
|
||||
_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset);
|
||||
_avatarData->unlock();
|
||||
}
|
||||
|
||||
unlock();
|
||||
_avatarData->unlock();
|
||||
_entityTree->unlock();
|
||||
|
||||
computeCollisionEvents();
|
||||
|
@ -498,10 +504,8 @@ void PhysicsEngine::removeObjectFromBullet(ObjectMotionState* motionState) {
|
|||
btRigidBody* body = motionState->getRigidBody();
|
||||
if (body) {
|
||||
const btCollisionShape* shape = body->getCollisionShape();
|
||||
ShapeInfo shapeInfo;
|
||||
ShapeInfoUtil::collectInfoFromShape(shape, shapeInfo);
|
||||
_dynamicsWorld->removeRigidBody(body);
|
||||
_shapeManager.releaseShape(shapeInfo);
|
||||
_shapeManager.releaseShape(shape);
|
||||
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||
motionState->setRigidBody(NULL);
|
||||
delete body;
|
||||
|
|
|
@ -26,6 +26,9 @@ int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) {
|
|||
case SHAPE_TYPE_CAPSULE_Y:
|
||||
bulletShapeType = CAPSULE_SHAPE_PROXYTYPE;
|
||||
break;
|
||||
case SHAPE_TYPE_CONVEX_HULL:
|
||||
bulletShapeType = CONVEX_HULL_SHAPE_PROXYTYPE;
|
||||
break;
|
||||
}
|
||||
return bulletShapeType;
|
||||
}
|
||||
|
@ -42,6 +45,9 @@ int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) {
|
|||
case CAPSULE_SHAPE_PROXYTYPE:
|
||||
shapeInfoType = SHAPE_TYPE_CAPSULE_Y;
|
||||
break;
|
||||
case CONVEX_HULL_SHAPE_PROXYTYPE:
|
||||
shapeInfoType = SHAPE_TYPE_CONVEX_HULL;
|
||||
break;
|
||||
}
|
||||
return shapeInfoType;
|
||||
}
|
||||
|
@ -60,8 +66,21 @@ void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInf
|
|||
info.setSphere(sphereShape->getRadius());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
case SHAPE_TYPE_CONVEX_HULL: {
|
||||
const btConvexHullShape* convexHullShape = static_cast<const btConvexHullShape*>(shape);
|
||||
const int numPoints = convexHullShape->getNumPoints();
|
||||
const btVector3* btPoints = convexHullShape->getUnscaledPoints();
|
||||
QVector<glm::vec3> points;
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ());
|
||||
points << point;
|
||||
}
|
||||
info.setConvexHull(points);
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
info.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
@ -88,6 +107,15 @@ btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) {
|
|||
shape = new btCapsuleShape(radius, height);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CONVEX_HULL: {
|
||||
shape = new btConvexHullShape();
|
||||
const QVector<glm::vec3>& points = info.getPoints();
|
||||
foreach (glm::vec3 point, points) {
|
||||
btVector3 btPoint(point[0], point[1], point[2]);
|
||||
static_cast<btConvexHullShape*>(shape)->addPoint(btPoint);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
|
|
@ -1770,8 +1770,8 @@ void GeometryCache::renderLine(const glm::vec2& p1, const glm::vec2& p2,
|
|||
}
|
||||
|
||||
|
||||
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) {
|
||||
return getResource(url, fallback, delayLoad).staticCast<NetworkGeometry>();
|
||||
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad, bool block) {
|
||||
return getResource(url, fallback, delayLoad, NULL, block).staticCast<NetworkGeometry>();
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url,
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
#include <DependencyManager.h>
|
||||
#include <ResourceCache.h>
|
||||
|
||||
#include <FBXReader.h>
|
||||
#include <OBJReader.h>
|
||||
#include "FBXReader.h"
|
||||
#include "OBJReader.h"
|
||||
|
||||
#include <AnimationCache.h>
|
||||
|
||||
|
@ -203,7 +203,8 @@ public:
|
|||
/// Loads geometry from the specified URL.
|
||||
/// \param fallback a fallback URL to load if the desired one is unavailable
|
||||
/// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested
|
||||
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
||||
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(),
|
||||
bool delayLoad = false, bool block = true);
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <gpu/GLBackend.h>
|
||||
#include <PathUtils.h>
|
||||
#include <PerfStat.h>
|
||||
#include <PhysicsEntity.h>
|
||||
#include "PhysicsEntity.h"
|
||||
#include <ShapeCollider.h>
|
||||
#include <SphereShape.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
@ -1101,6 +1101,14 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo
|
|||
}
|
||||
}
|
||||
|
||||
void Model::setCollisionModelURL(const QUrl& url, const QUrl& fallback, bool delayLoad) {
|
||||
if (_collisionUrl == url) {
|
||||
return;
|
||||
}
|
||||
_collisionUrl = url;
|
||||
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, fallback, delayLoad);
|
||||
}
|
||||
|
||||
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
||||
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
|
||||
return false;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <GeometryUtil.h>
|
||||
#include <gpu/Stream.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <PhysicsEntity.h>
|
||||
#include "PhysicsEntity.h"
|
||||
#include <Transform.h>
|
||||
|
||||
#include "AnimationHandle.h"
|
||||
|
@ -106,7 +106,10 @@ public:
|
|||
/// \param delayLoad if true, don't load the model immediately; wait until actually requested
|
||||
Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(),
|
||||
bool retainCurrent = false, bool delayLoad = false);
|
||||
|
||||
|
||||
// Set the model to use for collisions
|
||||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
||||
|
||||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
/// Sets the distance parameter used for LOD computations.
|
||||
|
@ -287,11 +290,14 @@ private:
|
|||
float _lodDistance;
|
||||
float _lodHysteresis;
|
||||
float _nextLODHysteresis;
|
||||
|
||||
QSharedPointer<NetworkGeometry> _collisionGeometry;
|
||||
|
||||
float _pupilDilation;
|
||||
QVector<float> _blendshapeCoefficients;
|
||||
|
||||
QUrl _url;
|
||||
QUrl _collisionUrl;
|
||||
|
||||
gpu::Buffers _blendedVertexBuffers;
|
||||
std::vector<Transform> _transforms;
|
||||
|
|
|
@ -19,10 +19,9 @@ void ShapeInfo::clear() {
|
|||
_type = SHAPE_TYPE_NONE;
|
||||
_halfExtents = glm::vec3(0.0f);
|
||||
_doubleHashKey.clear();
|
||||
_externalData = NULL;
|
||||
}
|
||||
|
||||
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QVector<glm::vec3>* data) {
|
||||
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
|
||||
_type = type;
|
||||
switch(type) {
|
||||
case SHAPE_TYPE_NONE:
|
||||
|
@ -37,10 +36,14 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QVector<
|
|||
_halfExtents = glm::vec3(radius);
|
||||
break;
|
||||
}
|
||||
case SHAPE_TYPE_CONVEX_HULL:
|
||||
_url = QUrl(url);
|
||||
_halfExtents = halfExtents;
|
||||
break;
|
||||
default:
|
||||
_halfExtents = halfExtents;
|
||||
break;
|
||||
}
|
||||
_externalData = data;
|
||||
}
|
||||
|
||||
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
|
||||
|
@ -61,6 +64,11 @@ void ShapeInfo::setEllipsoid(const glm::vec3& halfExtents) {
|
|||
_doubleHashKey.clear();
|
||||
}
|
||||
|
||||
void ShapeInfo::setConvexHull(const QVector<glm::vec3>& points) {
|
||||
_type = SHAPE_TYPE_CONVEX_HULL;
|
||||
_points = points;
|
||||
}
|
||||
|
||||
void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
|
||||
_type = SHAPE_TYPE_CAPSULE_Y;
|
||||
_halfExtents = glm::vec3(radius, halfHeight, radius);
|
||||
|
@ -99,82 +107,53 @@ float ShapeInfo::computeVolume() const {
|
|||
}
|
||||
|
||||
const DoubleHashKey& ShapeInfo::getHash() const {
|
||||
// NOTE: we cache the hash so we only ever need to compute it once for any valid ShapeInfo instance.
|
||||
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
|
||||
if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) {
|
||||
// cast this to non-const pointer so we can do our dirty work
|
||||
// The key is not yet cached therefore we must compute it! To this end we bypass the const-ness
|
||||
// of this method by grabbing a non-const pointer to "this" and a non-const reference to _doubleHashKey.
|
||||
ShapeInfo* thisPtr = const_cast<ShapeInfo*>(this);
|
||||
DoubleHashKey& key = thisPtr->_doubleHashKey;
|
||||
|
||||
// compute hash1
|
||||
// TODO?: provide lookup table for hash/hash2 of _type rather than recompute?
|
||||
uint32_t primeIndex = 0;
|
||||
thisPtr->_doubleHashKey.computeHash((uint32_t)_type, primeIndex++);
|
||||
key.computeHash((uint32_t)_type, primeIndex++);
|
||||
|
||||
const QVector<glm::vec3>* data = getData();
|
||||
if (data) {
|
||||
// if externalData exists we use it to continue the hash
|
||||
// compute hash1
|
||||
uint32_t hash = key.getHash();
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
||||
// so the cast to int produces a round() effect rather than a floor()
|
||||
uint32_t floatHash =
|
||||
DoubleHashKey::hashFunction((uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f), primeIndex++);
|
||||
hash ^= floatHash;
|
||||
}
|
||||
key.setHash(hash);
|
||||
|
||||
// compute hash2
|
||||
hash = key.getHash2();
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
||||
// so the cast to int produces a round() effect rather than a floor()
|
||||
uint32_t floatHash =
|
||||
DoubleHashKey::hashFunction2((uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f));
|
||||
hash += ~(floatHash << 17);
|
||||
hash ^= (floatHash >> 11);
|
||||
hash += (floatHash << 4);
|
||||
hash ^= (floatHash >> 7);
|
||||
hash += ~(floatHash << 10);
|
||||
hash = (hash << 16) | (hash >> 16);
|
||||
}
|
||||
key.setHash2(hash);
|
||||
|
||||
// compute hash
|
||||
uint32_t hash = _doubleHashKey.getHash();
|
||||
|
||||
glm::vec3 tmpData;
|
||||
int numData = data->size();
|
||||
for (int i = 0; i < numData; ++i) {
|
||||
tmpData = (*data)[i];
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
||||
// so the cast to int produces a round() effect rather than a floor()
|
||||
uint32_t floatHash =
|
||||
DoubleHashKey::hashFunction((uint32_t)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f), primeIndex++);
|
||||
hash ^= floatHash;
|
||||
}
|
||||
}
|
||||
thisPtr->_doubleHashKey.setHash(hash);
|
||||
|
||||
// compute hash2
|
||||
hash = _doubleHashKey.getHash2();
|
||||
for (int i = 0; i < numData; ++i) {
|
||||
tmpData = (*data)[i];
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
||||
// so the cast to int produces a round() effect rather than a floor()
|
||||
uint32_t floatHash =
|
||||
DoubleHashKey::hashFunction2((uint32_t)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f));
|
||||
hash += ~(floatHash << 17);
|
||||
hash ^= (floatHash >> 11);
|
||||
hash += (floatHash << 4);
|
||||
hash ^= (floatHash >> 7);
|
||||
hash += ~(floatHash << 10);
|
||||
hash = (hash << 16) | (hash >> 16);
|
||||
}
|
||||
}
|
||||
thisPtr->_doubleHashKey.setHash2(hash);
|
||||
} else {
|
||||
// this shape info has no external data so type+extents should be enough to generate a unique hash
|
||||
// compute hash1
|
||||
uint32_t hash = _doubleHashKey.getHash();
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
||||
// so the cast to int produces a round() effect rather than a floor()
|
||||
uint32_t floatHash =
|
||||
DoubleHashKey::hashFunction((uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f), primeIndex++);
|
||||
hash ^= floatHash;
|
||||
}
|
||||
thisPtr->_doubleHashKey.setHash(hash);
|
||||
|
||||
// compute hash2
|
||||
hash = _doubleHashKey.getHash2();
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
||||
// so the cast to int produces a round() effect rather than a floor()
|
||||
uint32_t floatHash =
|
||||
DoubleHashKey::hashFunction2((uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f));
|
||||
hash += ~(floatHash << 17);
|
||||
hash ^= (floatHash >> 11);
|
||||
hash += (floatHash << 4);
|
||||
hash ^= (floatHash >> 7);
|
||||
hash += ~(floatHash << 10);
|
||||
hash = (hash << 16) | (hash >> 16);
|
||||
}
|
||||
thisPtr->_doubleHashKey.setHash2(hash);
|
||||
QString url = _url.toString();
|
||||
if (!url.isEmpty()) {
|
||||
// fold the urlHash into both parts
|
||||
QByteArray baUrl = url.toLocal8Bit();
|
||||
const char *cUrl = baUrl.data();
|
||||
uint32_t urlHash = qChecksum(cUrl, baUrl.count());
|
||||
key.setHash(key.getHash() ^ urlHash);
|
||||
key.setHash2(key.getHash2() ^ urlHash);
|
||||
}
|
||||
}
|
||||
return _doubleHashKey;
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#define hifi_ShapeInfo_h
|
||||
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "DoubleHashKey.h"
|
||||
|
@ -22,7 +24,7 @@ enum ShapeType {
|
|||
SHAPE_TYPE_BOX,
|
||||
SHAPE_TYPE_SPHERE,
|
||||
SHAPE_TYPE_ELLIPSOID,
|
||||
SHAPE_TYPE_HULL,
|
||||
SHAPE_TYPE_CONVEX_HULL,
|
||||
SHAPE_TYPE_PLANE,
|
||||
SHAPE_TYPE_COMPOUND,
|
||||
SHAPE_TYPE_CAPSULE_X,
|
||||
|
@ -34,22 +36,25 @@ enum ShapeType {
|
|||
};
|
||||
|
||||
class ShapeInfo {
|
||||
|
||||
public:
|
||||
void clear();
|
||||
|
||||
void setParams(ShapeType type, const glm::vec3& halfExtents, QVector<glm::vec3>* data = NULL);
|
||||
void setParams(ShapeType type, const glm::vec3& halfExtents, QString url="");
|
||||
void setBox(const glm::vec3& halfExtents);
|
||||
void setSphere(float radius);
|
||||
void setEllipsoid(const glm::vec3& halfExtents);
|
||||
//void setHull(); // TODO: implement this
|
||||
void setConvexHull(const QVector<glm::vec3>& points);
|
||||
void setCapsuleY(float radius, float halfHeight);
|
||||
|
||||
const int getType() const { return _type; }
|
||||
|
||||
const glm::vec3& getHalfExtents() const { return _halfExtents; }
|
||||
|
||||
void setData(const QVector<glm::vec3>* data) { _externalData = data; }
|
||||
const QVector<glm::vec3>* getData() const { return _externalData; }
|
||||
const QVector<glm::vec3>& getPoints() const { return _points; }
|
||||
|
||||
void clearPoints () { _points.clear(); }
|
||||
void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; }
|
||||
|
||||
float computeVolume() const;
|
||||
|
||||
|
@ -59,7 +64,8 @@ protected:
|
|||
ShapeType _type = SHAPE_TYPE_NONE;
|
||||
glm::vec3 _halfExtents = glm::vec3(0.0f);
|
||||
DoubleHashKey _doubleHashKey;
|
||||
const QVector<glm::vec3>* _externalData = NULL;
|
||||
QVector<glm::vec3> _points; // points for convex collision hull
|
||||
QUrl _url; // url for model of convex collision hull
|
||||
};
|
||||
|
||||
#endif // hifi_ShapeInfo_h
|
||||
|
|
Loading…
Reference in a new issue