mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-17 17:16:49 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into 20429
This commit is contained in:
commit
18b3a52f21
73 changed files with 1130 additions and 340 deletions
|
@ -149,7 +149,8 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
// about a given otherNode to this node
|
||||
// FIXME does this mean we should sort the othernodes by distance before iterating
|
||||
// over them?
|
||||
float outputBandwidth = node->getOutboundBandwidth();
|
||||
// float outputBandwidth =
|
||||
node->getOutboundBandwidth();
|
||||
|
||||
// this is an AGENT we have received head data from
|
||||
// send back a packet with other active node data to this node
|
||||
|
@ -169,7 +170,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
return true;
|
||||
},
|
||||
[&](const SharedNodePointer& otherNode) {
|
||||
AvatarMixerClientData* otherNodeData = otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
MutexTryLocker lock(otherNodeData->getMutex());
|
||||
if (!lock.isLocked()) {
|
||||
return;
|
||||
|
|
|
@ -45,6 +45,9 @@ var diceButton = Overlays.addOverlay("image", {
|
|||
|
||||
var GRAVITY = -3.5;
|
||||
var LIFETIME = 300;
|
||||
// NOTE: angularVelocity is in radians/sec
|
||||
var MAX_ANGULAR_SPEED = Math.PI;
|
||||
|
||||
function shootDice(position, velocity) {
|
||||
for (var i = 0; i < NUMBER_OF_DICE; i++) {
|
||||
dice.push(Entities.addEntity(
|
||||
|
@ -53,9 +56,7 @@ function shootDice(position, velocity) {
|
|||
position: position,
|
||||
velocity: velocity,
|
||||
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
|
||||
// NOTE: angularVelocity is in radians/sec
|
||||
var maxAngularSpeed = Math.PI;
|
||||
angularVelocity: { x: Math.random() * maxAngularSpeed, y: Math.random() * maxAngularSpeed, z: Math.random() * maxAngularSpeed },
|
||||
angularVelocity: { x: Math.random() * MAX_ANGULAR_SPEED, y: Math.random() * MAX_ANGULAR_SPEED, z: Math.random() * MAX_ANGULAR_SPEED },
|
||||
lifetime: LIFETIME,
|
||||
gravity: { x: 0, y: GRAVITY, z: 0 },
|
||||
shapeType: "box",
|
||||
|
|
|
@ -718,13 +718,8 @@ function mouseClickEvent(event) {
|
|||
var result = findClickedEntity(event);
|
||||
if (result) {
|
||||
var properties = Entities.getEntityProperties(result.entityID);
|
||||
var data = {};
|
||||
try {
|
||||
data = JSON.parse(properties.attribution);
|
||||
} catch (e) {
|
||||
}
|
||||
if (data.marketplaceID) {
|
||||
propertyMenu.marketplaceID = data.marketplaceID;
|
||||
if (properties.marketplaceID) {
|
||||
propertyMenu.marketplaceID = properties.marketplaceID;
|
||||
propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace");
|
||||
} else {
|
||||
propertyMenu.marketplaceID = null;
|
||||
|
@ -1360,7 +1355,7 @@ var propertyMenu = PopupMenu();
|
|||
|
||||
propertyMenu.onSelectMenuItem = function(name) {
|
||||
if (propertyMenu.marketplaceID) {
|
||||
var url = "https://metaverse.highfidelity.io/marketplace/items/" + propertyMenu.marketplaceID;
|
||||
var url = MARKETPLACE_URL + "/items/" + propertyMenu.marketplaceID;
|
||||
if (marketplaceWindow.url != url) {
|
||||
marketplaceWindow.setURL(url);
|
||||
}
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0);
|
||||
var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation()));
|
||||
|
||||
var housePositions = []
|
||||
for (var j = 0; j < measures.rows; j++) {
|
||||
|
||||
var posX1 = 0 - (xRange / 2);
|
||||
|
@ -87,11 +88,8 @@
|
|||
y: 0,
|
||||
z: dd
|
||||
};
|
||||
|
||||
print("House nr.:" + (houses.length + 1));
|
||||
houses.push(
|
||||
addHouseAt(Vec3.sum(housePos, posShift), (j % 2 == 0) ? rotEven : rotOdd)
|
||||
);
|
||||
|
||||
housePositions.push(Vec3.sum(housePos, posShift));
|
||||
posX1 += measures.parcelWidth;
|
||||
}
|
||||
}
|
||||
|
@ -144,14 +142,21 @@
|
|||
};
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
while (houses.length > 0) {
|
||||
if (!houses[0].isKnownID) {
|
||||
houses[0] = Entities.identifyEntity(houses[0]);
|
||||
}
|
||||
Entities.deleteEntity(houses.shift());
|
||||
var addHouses = function() {
|
||||
if (housePositions.length > 0) {
|
||||
position = housePositions.pop();
|
||||
print("House nr.:" + (houses.length + 1));
|
||||
houses.push(
|
||||
addHouseAt(position, (housePositions.length % 2 == 0) ? rotEven : rotOdd)
|
||||
);
|
||||
|
||||
// max 20 per second
|
||||
Script.setTimeout(addHouses, 50);
|
||||
} else {
|
||||
Script.stop();
|
||||
}
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
};
|
||||
|
||||
addHouses();
|
||||
|
||||
})();
|
||||
|
|
|
@ -140,7 +140,6 @@
|
|||
var elLifetime = document.getElementById("property-lifetime");
|
||||
var elScriptURL = document.getElementById("property-script-url");
|
||||
var elUserData = document.getElementById("property-user-data");
|
||||
var elAttribution = document.getElementById("property-attribution");
|
||||
|
||||
var elBoxSections = document.querySelectorAll(".box-section");
|
||||
var elBoxColorRed = document.getElementById("property-box-red");
|
||||
|
@ -211,6 +210,9 @@
|
|||
|
||||
disableChildren(document.getElementById("properties-list"), 'input');
|
||||
} else {
|
||||
var activeElement = document.activeElement;
|
||||
var selected = activeElement.selectionStart == 0 && activeElement.selectionEnd == activeElement.value.length;
|
||||
|
||||
var properties = data.selections[0].properties;
|
||||
|
||||
elID.innerHTML = properties.id;
|
||||
|
@ -264,7 +266,6 @@
|
|||
elLifetime.value = properties.lifetime;
|
||||
elScriptURL.value = properties.script;
|
||||
elUserData.value = properties.userData;
|
||||
elAttribution.value = properties.attribution;
|
||||
|
||||
if (properties.type != "Box") {
|
||||
for (var i = 0; i < elBoxSections.length; i++) {
|
||||
|
@ -337,6 +338,11 @@
|
|||
elLightExponent.value = properties.exponent;
|
||||
elLightCutoff.value = properties.cutoff;
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
activeElement.focus();
|
||||
activeElement.select();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -395,7 +401,6 @@
|
|||
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
|
||||
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
|
||||
elUserData.addEventListener('change', createEmitTextPropertyUpdateFunction('userData'));
|
||||
elAttribution.addEventListener('change', createEmitTextPropertyUpdateFunction('attribution'));
|
||||
|
||||
var boxColorChangeFunction = createEmitColorPropertyUpdateFunction(
|
||||
'color', elBoxColorRed, elBoxColorGreen, elBoxColorBlue);
|
||||
|
@ -630,13 +635,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Attribution</div>
|
||||
<div class="value">
|
||||
<textarea id="property-attribution"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="box-section property">
|
||||
<div class="label">Color</div>
|
||||
|
|
|
@ -92,11 +92,13 @@ var NotificationType = {
|
|||
SNAPSHOT: 2,
|
||||
WINDOW_RESIZE: 3,
|
||||
LOD_WARNING: 4,
|
||||
CONNECTION_REFUSED: 5,
|
||||
properties: [
|
||||
{ text: "Mute Toggle" },
|
||||
{ text: "Snapshot" },
|
||||
{ text: "Window Resize" },
|
||||
{ text: "Level of Detail" }
|
||||
{ text: "Level of Detail" },
|
||||
{ text: "Connection Refused" }
|
||||
],
|
||||
getTypeFromMenuItem: function(menuItemName) {
|
||||
if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) {
|
||||
|
@ -501,6 +503,10 @@ function onMuteStateChanged() {
|
|||
createNotification(muteString, NotificationType.MUTE_TOGGLE);
|
||||
}
|
||||
|
||||
function onDomainConnectionRefused(reason) {
|
||||
createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED );
|
||||
}
|
||||
|
||||
// handles mouse clicks on buttons
|
||||
function mousePressEvent(event) {
|
||||
var pickRay,
|
||||
|
@ -608,5 +614,6 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
|||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Menu.menuItemEvent.connect(menuItemEvent);
|
||||
Window.domainConnectionRefused.connect(onDomainConnectionRefused);
|
||||
|
||||
setup();
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
#include <PhysicsEngine.h>
|
||||
#include <ProgramObject.h>
|
||||
#include <ResourceCache.h>
|
||||
//#include <ScriptCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SoundCache.h>
|
||||
#include <TextRenderer.h>
|
||||
|
@ -221,7 +221,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
|
||||
auto geometryCache = DependencyManager::set<GeometryCache>();
|
||||
//auto scriptCache = DependencyManager::set<ScriptCache>();
|
||||
auto scriptCache = DependencyManager::set<ScriptCache>();
|
||||
auto soundCache = DependencyManager::set<SoundCache>();
|
||||
auto glowEffect = DependencyManager::set<GlowEffect>();
|
||||
auto faceshift = DependencyManager::set<Faceshift>();
|
||||
|
@ -297,7 +297,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_lastSendDownstreamAudioStats(usecTimestampNow()),
|
||||
_isVSyncOn(true),
|
||||
_aboutToQuit(false),
|
||||
_notifiedPacketVersionMismatchThisDomain(false)
|
||||
_notifiedPacketVersionMismatchThisDomain(false),
|
||||
_domainConnectionRefusals(QList<QString>())
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
installNativeEventFilter(&MyNativeEventFilter::getInstance());
|
||||
|
@ -344,6 +345,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
// put the NodeList and datagram processing on the node thread
|
||||
nodeList->moveToThread(nodeThread);
|
||||
|
||||
// geometry background downloads need to happen on the Datagram Processor Thread. The idle loop will
|
||||
// emit checkBackgroundDownloads to cause the GeometryCache to check it's queue for requested background
|
||||
// downloads.
|
||||
QSharedPointer<GeometryCache> geometryCacheP = DependencyManager::get<GeometryCache>();
|
||||
ResourceCache *geometryCache = geometryCacheP.data();
|
||||
connect(this, &Application::checkBackgroundDownloads, geometryCache, &ResourceCache::checkAsynchronousGets);
|
||||
|
||||
// connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, _datagramProcessor, &DatagramProcessor::processDatagrams);
|
||||
|
||||
|
@ -600,6 +608,7 @@ Application::~Application() {
|
|||
|
||||
Menu::getInstance()->deleteLater();
|
||||
|
||||
_physicsEngine.setCharacterController(NULL);
|
||||
_myAvatar = NULL;
|
||||
|
||||
ModelEntityItem::cleanupLoadedAnimations();
|
||||
|
@ -611,7 +620,7 @@ Application::~Application() {
|
|||
DependencyManager::destroy<AnimationCache>();
|
||||
DependencyManager::destroy<TextureCache>();
|
||||
DependencyManager::destroy<GeometryCache>();
|
||||
//DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<SoundCache>();
|
||||
|
||||
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
|
||||
|
@ -1515,7 +1524,7 @@ void Application::idle() {
|
|||
}
|
||||
|
||||
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
|
||||
// details if we're in ExtraDebugging mode. However, the ::update() and it's subcomponents will show their timing
|
||||
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
|
||||
// details normally.
|
||||
bool showWarnings = getLogger()->extraDebugging();
|
||||
PerformanceWarning warn(showWarnings, "idle()");
|
||||
|
@ -1556,6 +1565,9 @@ void Application::idle() {
|
|||
idleTimer->start(2);
|
||||
}
|
||||
}
|
||||
|
||||
// check for any requested background downloads.
|
||||
emit checkBackgroundDownloads();
|
||||
}
|
||||
|
||||
void Application::setFullscreen(bool fullscreen) {
|
||||
|
@ -2528,7 +2540,7 @@ bool Application::isHMDMode() const {
|
|||
QRect Application::getDesirableApplicationGeometry() {
|
||||
QRect applicationGeometry = getWindow()->geometry();
|
||||
|
||||
// If our parent window is on the HMD, then don't use it's geometry, instead use
|
||||
// If our parent window is on the HMD, then don't use its geometry, instead use
|
||||
// the "main screen" geometry.
|
||||
HMDToolsDialog* hmdTools = DependencyManager::get<DialogsManager>()->getHMDToolsDialog();
|
||||
if (hmdTools && hmdTools->hasHMDScreen()) {
|
||||
|
@ -3125,7 +3137,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
|
|||
int viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
|
||||
bool eyeRelativeCamera = false;
|
||||
// bool eyeRelativeCamera = false;
|
||||
if (billboard) {
|
||||
_mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); // degees
|
||||
_mirrorCamera.setPosition(_myAvatar->getPosition() +
|
||||
|
@ -3278,6 +3290,14 @@ void Application::clearDomainOctreeDetails() {
|
|||
void Application::domainChanged(const QString& domainHostname) {
|
||||
updateWindowTitle();
|
||||
clearDomainOctreeDetails();
|
||||
_domainConnectionRefusals.clear();
|
||||
}
|
||||
|
||||
void Application::domainConnectionDenied(const QString& reason) {
|
||||
if (!_domainConnectionRefusals.contains(reason)) {
|
||||
_domainConnectionRefusals.append(reason);
|
||||
emit domainConnectionRefused(reason);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::connectedToDomain(const QString& hostname) {
|
||||
|
@ -3356,7 +3376,7 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
|
||||
void Application::trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket) {
|
||||
|
||||
// Attempt to identify the sender from it's address.
|
||||
// Attempt to identify the sender from its address.
|
||||
if (sendingNode) {
|
||||
QUuid nodeUUID = sendingNode->getUUID();
|
||||
|
||||
|
@ -3425,7 +3445,7 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin
|
|||
}
|
||||
// store jurisdiction details for later use
|
||||
// This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it
|
||||
// but OctreeSceneStats thinks it's just returning a reference to it's contents. So we need to make a copy of the
|
||||
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
|
||||
// details from the OctreeSceneStats to construct the JurisdictionMap
|
||||
JurisdictionMap jurisdictionMap;
|
||||
jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes());
|
||||
|
@ -3645,17 +3665,56 @@ bool Application::askToSetAvatarUrl(const QString& url) {
|
|||
msgBox.exec();
|
||||
return false;
|
||||
}
|
||||
|
||||
QString message = "Would you like to use this model for part of avatar:\n" + url;
|
||||
|
||||
// Download the FST file, to attempt to determine its model type
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest = QNetworkRequest(url);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
QNetworkReply* reply = networkAccessManager.get(networkRequest);
|
||||
qDebug() << "Downloading avatar file at " << url;
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
QByteArray fstContents = reply->readAll();
|
||||
delete reply;
|
||||
QVariantHash fstMapping = FSTReader::readMapping(fstContents);
|
||||
|
||||
FSTReader::ModelType modelType = FSTReader::predictModelType(fstMapping);
|
||||
|
||||
QMessageBox msgBox;
|
||||
|
||||
msgBox.setIcon(QMessageBox::Question);
|
||||
msgBox.setWindowTitle("Set Avatar");
|
||||
msgBox.setText(message);
|
||||
QPushButton* headButton = NULL;
|
||||
QPushButton* bodyButton = NULL;
|
||||
QPushButton* bodyAndHeadButton = NULL;
|
||||
|
||||
QString message;
|
||||
QString typeInfo;
|
||||
switch (modelType) {
|
||||
case FSTReader::HEAD_MODEL:
|
||||
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar head?");
|
||||
headButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
|
||||
break;
|
||||
|
||||
QPushButton* headButton = msgBox.addButton(tr("Head"), QMessageBox::ActionRole);
|
||||
QPushButton* bodyButton = msgBox.addButton(tr("Body"), QMessageBox::ActionRole);
|
||||
QPushButton* bodyAndHeadButton = msgBox.addButton(tr("Body + Head"), QMessageBox::ActionRole);
|
||||
case FSTReader::BODY_ONLY_MODEL:
|
||||
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar body?");
|
||||
bodyButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
|
||||
break;
|
||||
|
||||
case FSTReader::HEAD_AND_BODY_MODEL:
|
||||
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar?");
|
||||
bodyAndHeadButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
|
||||
break;
|
||||
|
||||
default:
|
||||
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for some part of your avatar head?");
|
||||
headButton = msgBox.addButton(tr("Use for Head"), QMessageBox::ActionRole);
|
||||
bodyButton = msgBox.addButton(tr("Use for Body"), QMessageBox::ActionRole);
|
||||
bodyAndHeadButton = msgBox.addButton(tr("Use for Body and Head"), QMessageBox::ActionRole);
|
||||
break;
|
||||
}
|
||||
|
||||
msgBox.setText(message);
|
||||
msgBox.addButton(QMessageBox::Cancel);
|
||||
|
||||
msgBox.exec();
|
||||
|
@ -3668,6 +3727,11 @@ bool Application::askToSetAvatarUrl(const QString& url) {
|
|||
} else if (msgBox.clickedButton() == bodyButton) {
|
||||
qDebug() << "Chose to use for body: " << url;
|
||||
_myAvatar->setSkeletonModelURL(url);
|
||||
// if the head is empty, reset it to the default head.
|
||||
if (_myAvatar->getFaceModelURLString().isEmpty()) {
|
||||
_myAvatar->setFaceModelURL(DEFAULT_HEAD_MODEL_URL);
|
||||
UserActivityLogger::getInstance().changedModel("head", DEFAULT_HEAD_MODEL_URL.toString());
|
||||
}
|
||||
UserActivityLogger::getInstance().changedModel("skeleton", url);
|
||||
_myAvatar->sendIdentityPacket();
|
||||
} else if (msgBox.clickedButton() == bodyAndHeadButton) {
|
||||
|
|
|
@ -333,6 +333,9 @@ signals:
|
|||
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
void checkBackgroundDownloads();
|
||||
void domainConnectionRefused(const QString& reason);
|
||||
|
||||
public slots:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
void updateWindowTitle();
|
||||
|
@ -383,6 +386,8 @@ public slots:
|
|||
|
||||
void setActiveFaceTracker();
|
||||
|
||||
void domainConnectionDenied(const QString& reason);
|
||||
|
||||
private slots:
|
||||
void clearDomainOctreeDetails();
|
||||
void checkFPS();
|
||||
|
@ -607,6 +612,8 @@ private:
|
|||
int _menuBarHeight;
|
||||
|
||||
QHash<QString, AcceptURLMethod> _acceptedExtensions;
|
||||
|
||||
QList<QString> _domainConnectionRefusals;
|
||||
};
|
||||
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -127,6 +127,7 @@ void DatagramProcessor::processDatagrams() {
|
|||
// and check and signal for an access token so that we can make sure they are logged in
|
||||
qDebug() << "The domain-server denied a connection request: " << reason;
|
||||
qDebug() << "You may need to re-log to generate a keypair so you can provide a username signature.";
|
||||
application->domainConnectionDenied(reason);
|
||||
AccountManager::getInstance().checkAndSignalForAccessToken();
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -246,7 +246,7 @@ namespace MenuOption {
|
|||
const QString ToolWindow = "Tool Window";
|
||||
const QString TransmitterDrive = "Transmitter Drive";
|
||||
const QString TurnWithHead = "Turn using Head";
|
||||
const QString PackageModel = "Package Model";
|
||||
const QString PackageModel = "Package Model...";
|
||||
const QString Visage = "Visage";
|
||||
const QString Wireframe = "Wireframe";
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ bool ModelPackager::loadModel() {
|
|||
return false;
|
||||
}
|
||||
qDebug() << "Reading FST file : " << _modelFile.filePath();
|
||||
_mapping = readMapping(fst.readAll());
|
||||
_mapping = FSTReader::readMapping(fst.readAll());
|
||||
fst.close();
|
||||
|
||||
_fbxInfo = QFileInfo(_modelFile.path() + "/" + _mapping.value(FILENAME_FIELD).toString());
|
||||
|
@ -119,21 +119,23 @@ bool ModelPackager::editProperties() {
|
|||
return false;
|
||||
}
|
||||
_mapping = properties.getMapping();
|
||||
|
||||
// Make sure that a mapping for the root joint has been specified
|
||||
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
|
||||
if (!joints.contains("jointRoot")) {
|
||||
qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName());
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
// Make sure that a mapping for the root joint has been specified
|
||||
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
|
||||
if (!joints.contains("jointRoot")) {
|
||||
qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName());
|
||||
|
||||
QString message = "Your did not configure a root joint for your skeleton model.\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();
|
||||
QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled.";
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Model Packager");
|
||||
msgBox.setText(message);
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.exec();
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -183,7 +185,7 @@ bool ModelPackager::zipModel() {
|
|||
// Copy FST
|
||||
QFile fst(tempDir.path() + "/" + nameField + ".fst");
|
||||
if (fst.open(QIODevice::WriteOnly)) {
|
||||
fst.write(writeMapping(_mapping));
|
||||
fst.write(FSTReader::writeMapping(_mapping));
|
||||
fst.close();
|
||||
} else {
|
||||
qDebug() << "Couldn't write FST file" << fst.fileName();
|
||||
|
@ -204,6 +206,18 @@ bool ModelPackager::zipModel() {
|
|||
}
|
||||
|
||||
void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) {
|
||||
|
||||
bool isBodyType = _modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL;
|
||||
|
||||
// mixamo files - 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_Right") &&
|
||||
geometry.blendshapeChannelNames.contains("MouthOpen") &&
|
||||
geometry.blendshapeChannelNames.contains("Blink_Left") &&
|
||||
geometry.blendshapeChannelNames.contains("Blink_Right") &&
|
||||
geometry.blendshapeChannelNames.contains("Squint_Right"));
|
||||
|
||||
if (!mapping.contains(NAME_FIELD)) {
|
||||
mapping.insert(NAME_FIELD, QFileInfo(filename).baseName());
|
||||
}
|
||||
|
@ -232,39 +246,40 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
|
|||
if (!joints.contains("jointNeck")) {
|
||||
joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck");
|
||||
}
|
||||
if (!joints.contains("jointRoot")) {
|
||||
joints.insert("jointRoot", "Hips");
|
||||
}
|
||||
if (!joints.contains("jointLean")) {
|
||||
joints.insert("jointLean", "Spine");
|
||||
|
||||
if (isBodyType) {
|
||||
if (!joints.contains("jointRoot")) {
|
||||
joints.insert("jointRoot", "Hips");
|
||||
}
|
||||
if (!joints.contains("jointLean")) {
|
||||
joints.insert("jointLean", "Spine");
|
||||
}
|
||||
if (!joints.contains("jointLeftHand")) {
|
||||
joints.insert("jointLeftHand", "LeftHand");
|
||||
}
|
||||
if (!joints.contains("jointRightHand")) {
|
||||
joints.insert("jointRightHand", "RightHand");
|
||||
}
|
||||
}
|
||||
|
||||
if (!joints.contains("jointHead")) {
|
||||
const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd";
|
||||
const char* topName = likelyMixamoFile ? "HeadTop_End" : "HeadEnd";
|
||||
joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head");
|
||||
}
|
||||
if (!joints.contains("jointLeftHand")) {
|
||||
joints.insert("jointLeftHand", "LeftHand");
|
||||
}
|
||||
if (!joints.contains("jointRightHand")) {
|
||||
joints.insert("jointRightHand", "RightHand");
|
||||
}
|
||||
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
if (!mapping.contains(FREE_JOINT_FIELD)) {
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
|
||||
|
||||
if (isBodyType) {
|
||||
if (!mapping.contains(FREE_JOINT_FIELD)) {
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
|
||||
}
|
||||
}
|
||||
|
||||
// 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_Right") &&
|
||||
geometry.blendshapeChannelNames.contains("MouthOpen") &&
|
||||
geometry.blendshapeChannelNames.contains("Blink_Left") &&
|
||||
geometry.blendshapeChannelNames.contains("Blink_Right") &&
|
||||
geometry.blendshapeChannelNames.contains("Squint_Right"));
|
||||
|
||||
// If there are no blendshape mappings, and we detect that this is likely a mixamo file,
|
||||
// then we can add the default mixamo to "faceshift" mappings
|
||||
if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) {
|
||||
QVariantHash blendshapes;
|
||||
blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);
|
||||
|
|
|
@ -35,7 +35,7 @@ private:
|
|||
|
||||
QFileInfo _modelFile;
|
||||
QFileInfo _fbxInfo;
|
||||
ModelType _modelType;
|
||||
FSTReader::ModelType _modelType;
|
||||
QString _texDir;
|
||||
|
||||
QVariantHash _mapping;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#include "ModelPropertiesDialog.h"
|
||||
|
||||
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
|
||||
const QString& basePath, const FBXGeometry& geometry) :
|
||||
_modelType(modelType),
|
||||
_originalMapping(originalMapping),
|
||||
|
@ -46,8 +46,8 @@ _geometry(geometry)
|
|||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setSingleStep(0.01);
|
||||
|
||||
if (_modelType != ENTITY_MODEL) {
|
||||
if (_modelType == ATTACHMENT_MODEL) {
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
QHBoxLayout* translation = new QHBoxLayout();
|
||||
form->addRow("Translation:", translation);
|
||||
translation->addWidget(_translationX = createTranslationBox());
|
||||
|
@ -63,7 +63,7 @@ _geometry(geometry)
|
|||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||
}
|
||||
if (_modelType == SKELETON_MODEL) {
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
||||
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
||||
form->addRow("Head Joint:", _headJoint = createJointBox());
|
||||
|
@ -89,8 +89,14 @@ _geometry(geometry)
|
|||
reset();
|
||||
}
|
||||
|
||||
|
||||
QString ModelPropertiesDialog::getType() const {
|
||||
return FSTReader::getNameFromType(_modelType);
|
||||
}
|
||||
|
||||
QVariantHash ModelPropertiesDialog::getMapping() const {
|
||||
QVariantHash mapping = _originalMapping;
|
||||
mapping.insert(TYPE_FIELD, getType());
|
||||
mapping.insert(NAME_FIELD, _name->text());
|
||||
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
|
||||
mapping.insert(SCALE_FIELD, QString::number(_scale->value()));
|
||||
|
@ -102,9 +108,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
|
|||
}
|
||||
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
|
||||
|
||||
if (_modelType != ENTITY_MODEL) {
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||
if (_modelType == ATTACHMENT_MODEL) {
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
glm::vec3 pivot;
|
||||
if (_pivotAboutCenter->isChecked()) {
|
||||
pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f;
|
||||
|
@ -121,7 +127,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
|
|||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||
}
|
||||
if (_modelType == SKELETON_MODEL) {
|
||||
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
||||
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
||||
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
||||
|
@ -151,8 +159,8 @@ void ModelPropertiesDialog::reset() {
|
|||
|
||||
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
|
||||
|
||||
if (_modelType != ENTITY_MODEL) {
|
||||
if (_modelType == ATTACHMENT_MODEL) {
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
|
||||
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
|
||||
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
|
||||
|
@ -164,7 +172,8 @@ void ModelPropertiesDialog::reset() {
|
|||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||
}
|
||||
if (_modelType == SKELETON_MODEL) {
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
||||
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
||||
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QDialog>
|
||||
|
||||
#include <FBXReader.h>
|
||||
#include <FSTReader.h>
|
||||
|
||||
#include "ui/ModelsBrowser.h"
|
||||
|
||||
|
@ -28,7 +29,7 @@ class ModelPropertiesDialog : public QDialog {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
|
||||
ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
|
||||
const QString& basePath, const FBXGeometry& geometry);
|
||||
|
||||
QVariantHash getMapping() const;
|
||||
|
@ -43,8 +44,9 @@ private:
|
|||
QComboBox* createJointBox(bool withNone = true) const;
|
||||
QDoubleSpinBox* createTranslationBox() const;
|
||||
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
|
||||
QString getType() const;
|
||||
|
||||
ModelType _modelType;
|
||||
FSTReader::ModelType _modelType;
|
||||
QVariantHash _originalMapping;
|
||||
QString _basePath;
|
||||
FBXGeometry _geometry;
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
|
||||
#include "ModelSelector.h"
|
||||
|
||||
static const QString AVATAR_HEAD_STRING = "Avatar Head";
|
||||
static const QString AVATAR_BODY_STRING = "Avatar Body";
|
||||
static const QString AVATAR_HEAD_STRING = "Avatar Head Only";
|
||||
static const QString AVATAR_BODY_STRING = "Avatar Body Only";
|
||||
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
|
||||
static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
|
||||
static const QString ENTITY_MODEL_STRING = "Entity Model";
|
||||
|
||||
|
@ -36,6 +37,7 @@ ModelSelector::ModelSelector() {
|
|||
_modelType = new QComboBox(this);
|
||||
_modelType->addItem(AVATAR_HEAD_STRING);
|
||||
_modelType->addItem(AVATAR_BODY_STRING);
|
||||
_modelType->addItem(AVATAR_HEAD_AND_BODY_STRING);
|
||||
_modelType->addItem(AVATAR_ATTACHEMENT_STRING);
|
||||
_modelType->addItem(ENTITY_MODEL_STRING);
|
||||
form->addRow("Model Type:", _modelType);
|
||||
|
@ -50,17 +52,19 @@ QFileInfo ModelSelector::getFileInfo() const {
|
|||
return _modelFile;
|
||||
}
|
||||
|
||||
ModelType ModelSelector::getModelType() const {
|
||||
FSTReader::ModelType ModelSelector::getModelType() const {
|
||||
QString text = _modelType->currentText();
|
||||
|
||||
if (text == AVATAR_HEAD_STRING) {
|
||||
return HEAD_MODEL;
|
||||
return FSTReader::HEAD_MODEL;
|
||||
} else if (text == AVATAR_BODY_STRING) {
|
||||
return SKELETON_MODEL;
|
||||
return FSTReader::BODY_ONLY_MODEL;
|
||||
} else if (text == AVATAR_HEAD_AND_BODY_STRING) {
|
||||
return FSTReader::HEAD_AND_BODY_MODEL;
|
||||
} else if (text == AVATAR_ATTACHEMENT_STRING) {
|
||||
return ATTACHMENT_MODEL;
|
||||
return FSTReader::ATTACHMENT_MODEL;
|
||||
} else if (text == ENTITY_MODEL_STRING) {
|
||||
return ENTITY_MODEL;
|
||||
return FSTReader::ENTITY_MODEL;
|
||||
} else {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
ModelSelector();
|
||||
|
||||
QFileInfo getFileInfo() const;
|
||||
ModelType getModelType() const;
|
||||
FSTReader::ModelType getModelType() const;
|
||||
|
||||
public slots:
|
||||
virtual void accept();
|
||||
|
|
|
@ -353,7 +353,7 @@ void InputController::update() {
|
|||
// TODO for now the InputController is only supporting a JointTracker from a MotionTracker
|
||||
MotionTracker* motionTracker = dynamic_cast< MotionTracker*> (DeviceTracker::getDevice(_deviceTrackerId));
|
||||
if (motionTracker) {
|
||||
if (_subTrackerId < motionTracker->numJointTrackers()) {
|
||||
if ((int)_subTrackerId < motionTracker->numJointTrackers()) {
|
||||
const MotionTracker::JointTracker* joint = motionTracker->getJointTracker(_subTrackerId);
|
||||
|
||||
if (joint->isActive()) {
|
||||
|
|
|
@ -33,6 +33,7 @@ WindowScriptingInterface::WindowScriptingInterface() :
|
|||
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &WindowScriptingInterface::domainChanged);
|
||||
connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested);
|
||||
connect(Application::getInstance(), &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
|
||||
}
|
||||
|
||||
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) {
|
||||
|
@ -637,7 +638,7 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
|
|||
/// \param const QString& nameFilter filter to filter filenames
|
||||
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
|
||||
QScriptValue WindowScriptingInterface::showS3Browse(const QString& nameFilter) {
|
||||
ModelsBrowser browser(ENTITY_MODEL);
|
||||
ModelsBrowser browser(FSTReader::ENTITY_MODEL);
|
||||
if (nameFilter != "") {
|
||||
browser.setNameFilter(nameFilter);
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ signals:
|
|||
void inlineButtonClicked(const QString& name);
|
||||
void nonBlockingFormClosed();
|
||||
void svoImportRequested(const QString& url);
|
||||
void domainConnectionRefused(const QString& reason);
|
||||
|
||||
private slots:
|
||||
QScriptValue showAlert(const QString& message);
|
||||
|
|
|
@ -139,6 +139,7 @@ ApplicationOverlay::ApplicationOverlay() :
|
|||
_magnifier(true),
|
||||
_alpha(1.0f),
|
||||
_oculusUIRadius(1.0f),
|
||||
_trailingAudioLoudness(0.0f),
|
||||
_crosshairTexture(0),
|
||||
_previousBorderWidth(-1),
|
||||
_previousBorderHeight(-1),
|
||||
|
|
|
@ -164,7 +164,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const {
|
|||
}
|
||||
|
||||
void AttachmentPanel::chooseModelURL() {
|
||||
ModelsBrowser modelBrowser(ATTACHMENT_MODEL, this);
|
||||
ModelsBrowser modelBrowser(FSTReader::ATTACHMENT_MODEL, this);
|
||||
connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&)));
|
||||
modelBrowser.browse();
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
#include "ModelsBrowser.h"
|
||||
|
||||
const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "attachments" };
|
||||
const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "skeletons", "attachments" };
|
||||
|
||||
static const QString S3_URL = "http://s3.amazonaws.com/hifi-public";
|
||||
static const QString PUBLIC_URL = "http://public.highfidelity.io";
|
||||
|
@ -71,7 +71,7 @@ static const QString propertiesIds[MODEL_METADATA_COUNT] = {
|
|||
"Tags"
|
||||
};
|
||||
|
||||
ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) :
|
||||
ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) :
|
||||
QWidget(parent, Qt::WindowStaysOnTopHint),
|
||||
_handler(new ModelHandler(modelsType))
|
||||
{
|
||||
|
@ -184,7 +184,7 @@ void ModelsBrowser::browse() {
|
|||
}
|
||||
|
||||
|
||||
ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) :
|
||||
ModelHandler::ModelHandler(FSTReader::ModelType modelsType, QWidget* parent) :
|
||||
QObject(parent),
|
||||
_initiateExit(false),
|
||||
_type(modelsType),
|
||||
|
|
|
@ -16,21 +16,16 @@
|
|||
#include <QStandardItemModel>
|
||||
#include <QTreeView>
|
||||
|
||||
class QNetworkReply;
|
||||
#include <FSTReader.h>
|
||||
|
||||
enum ModelType {
|
||||
ENTITY_MODEL,
|
||||
HEAD_MODEL,
|
||||
SKELETON_MODEL,
|
||||
ATTACHMENT_MODEL
|
||||
};
|
||||
class QNetworkReply;
|
||||
|
||||
extern const char* MODEL_TYPE_NAMES[];
|
||||
|
||||
class ModelHandler : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ModelHandler(ModelType modelsType, QWidget* parent = NULL);
|
||||
ModelHandler(FSTReader::ModelType modelsType, QWidget* parent = NULL);
|
||||
|
||||
void lockModel() { _lock.lockForRead(); }
|
||||
QStandardItemModel* getModel() { return &_model; }
|
||||
|
@ -51,7 +46,7 @@ private slots:
|
|||
|
||||
private:
|
||||
bool _initiateExit;
|
||||
ModelType _type;
|
||||
FSTReader::ModelType _type;
|
||||
QReadWriteLock _lock;
|
||||
QStandardItemModel _model;
|
||||
QString _nameFilter;
|
||||
|
@ -66,7 +61,7 @@ class ModelsBrowser : public QWidget {
|
|||
Q_OBJECT
|
||||
public:
|
||||
|
||||
ModelsBrowser(ModelType modelsType, QWidget* parent = NULL);
|
||||
ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent = NULL);
|
||||
QString getSelectedFile() { return _selectedFile; }
|
||||
|
||||
signals:
|
||||
|
|
|
@ -67,13 +67,13 @@ void PreferencesDialog::setSkeletonUrl(QString modelUrl) {
|
|||
}
|
||||
|
||||
void PreferencesDialog::openHeadModelBrowser() {
|
||||
ModelsBrowser modelBrowser(HEAD_MODEL);
|
||||
ModelsBrowser modelBrowser(FSTReader::HEAD_MODEL);
|
||||
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl);
|
||||
modelBrowser.browse();
|
||||
}
|
||||
|
||||
void PreferencesDialog::openBodyModelBrowser() {
|
||||
ModelsBrowser modelBrowser(SKELETON_MODEL);
|
||||
ModelsBrowser modelBrowser(FSTReader::HEAD_AND_BODY_MODEL);
|
||||
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl);
|
||||
modelBrowser.browse();
|
||||
}
|
||||
|
|
|
@ -88,9 +88,9 @@ template< typename T >
|
|||
void AudioFrameBuffer< T >::deallocateFrames() {
|
||||
if (_frameBuffer) {
|
||||
for (uint32_t i = 0; i < _channelCountMax; ++i) {
|
||||
delete _frameBuffer[i];
|
||||
delete[] _frameBuffer[i];
|
||||
}
|
||||
delete _frameBuffer;
|
||||
delete[] _frameBuffer;
|
||||
}
|
||||
_frameBuffer = NULL;
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ public:
|
|||
}
|
||||
|
||||
void loadProfile(int profileIndex) {
|
||||
if (profileIndex >= 0 && profileIndex < _profileCount) {
|
||||
if (profileIndex >= 0 && profileIndex < (int)_profileCount) {
|
||||
|
||||
for (uint32_t i = 0; i < _filterCount; ++i) {
|
||||
FilterParameter p = _profiles[profileIndex][i];
|
||||
|
|
|
@ -110,14 +110,30 @@ void EntityTreeRenderer::shutdown() {
|
|||
_shuttingDown = true;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
||||
if (_waitingOnPreload.contains(url)) {
|
||||
QList<EntityItemID> entityIDs = _waitingOnPreload.values(url);
|
||||
_waitingOnPreload.remove(url);
|
||||
foreach(EntityItemID entityID, entityIDs) {
|
||||
checkAndCallPreload(entityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) {
|
||||
void EntityTreeRenderer::errorInLoadingScript(const QUrl& url) {
|
||||
if (_waitingOnPreload.contains(url)) {
|
||||
_waitingOnPreload.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID, bool isPreload) {
|
||||
EntityItem* entity = static_cast<EntityTree*>(_tree)->findEntityByEntityItemID(entityItemID);
|
||||
return loadEntityScript(entity);
|
||||
return loadEntityScript(entity, isPreload);
|
||||
}
|
||||
|
||||
|
||||
QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL) {
|
||||
QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& urlOut) {
|
||||
isPending = false;
|
||||
QUrl url(scriptMaybeURLorText);
|
||||
|
||||
// If the url is not valid, this must be script text...
|
||||
|
@ -126,6 +142,7 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
|
|||
return scriptMaybeURLorText;
|
||||
}
|
||||
isURL = true;
|
||||
urlOut = url;
|
||||
|
||||
QString scriptContents; // assume empty
|
||||
|
||||
|
@ -148,20 +165,11 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
|
|||
qDebug() << "ERROR Loading file:" << fileName;
|
||||
}
|
||||
} else {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest = QNetworkRequest(url);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
QNetworkReply* reply = networkAccessManager.get(networkRequest);
|
||||
qDebug() << "Downloading script at" << url;
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
|
||||
scriptContents = reply->readAll();
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << url.toString();
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
|
||||
if (!scriptCache->isInBadScriptList(url)) {
|
||||
scriptContents = scriptCache->getScript(url, this, isPending);
|
||||
}
|
||||
delete reply;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +177,7 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
|
|||
}
|
||||
|
||||
|
||||
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
|
||||
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity, bool isPreload) {
|
||||
if (_shuttingDown) {
|
||||
return QScriptValue(); // since we're shutting down, we don't load any more scripts
|
||||
}
|
||||
|
@ -203,7 +211,24 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
|
|||
}
|
||||
|
||||
bool isURL = false; // loadScriptContents() will tell us if this is a URL or just text.
|
||||
QString scriptContents = loadScriptContents(entityScript, isURL);
|
||||
bool isPending = false;
|
||||
QUrl url;
|
||||
QString scriptContents = loadScriptContents(entityScript, isURL, isPending, url);
|
||||
|
||||
if (isPending && isPreload && isURL) {
|
||||
_waitingOnPreload.insert(url, entityID);
|
||||
|
||||
}
|
||||
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
|
||||
if (isURL && scriptCache->isInBadScriptList(url)) {
|
||||
return QScriptValue(); // no script contents...
|
||||
}
|
||||
|
||||
if (scriptContents.isEmpty()) {
|
||||
return QScriptValue(); // no script contents...
|
||||
}
|
||||
|
||||
QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(scriptContents);
|
||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||
|
@ -211,6 +236,9 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
|
|||
qDebug() << " " << syntaxCheck.errorMessage() << ":"
|
||||
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
|
||||
qDebug() << " SCRIPT:" << entityScript;
|
||||
|
||||
scriptCache->addScriptToBadScriptList(url);
|
||||
|
||||
return QScriptValue(); // invalid script
|
||||
}
|
||||
|
||||
|
@ -223,6 +251,9 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
|
|||
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
|
||||
qDebug() << " NOT CONSTRUCTOR";
|
||||
qDebug() << " SCRIPT:" << entityScript;
|
||||
|
||||
scriptCache->addScriptToBadScriptList(url);
|
||||
|
||||
return QScriptValue(); // invalid script
|
||||
} else {
|
||||
entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents);
|
||||
|
@ -920,7 +951,7 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID) {
|
|||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
// load the entity script if needed...
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
QScriptValue entityScript = loadEntityScript(entityID, true); // is preload!
|
||||
if (entityScript.property("preload").isValid()) {
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
entityScript.property("preload").call(entityScript, entityArgs);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
|
||||
#include <MouseEvent.h>
|
||||
#include <OctreeRenderer.h>
|
||||
#include <ScriptCache.h>
|
||||
|
||||
class Model;
|
||||
class ScriptEngine;
|
||||
|
@ -31,7 +32,7 @@ public:
|
|||
};
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
|
||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
||||
|
@ -84,6 +85,9 @@ public:
|
|||
/// hovering over, and entering entities
|
||||
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
|
||||
|
||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
||||
virtual void errorInLoadingScript(const QUrl& url);
|
||||
|
||||
signals:
|
||||
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
||||
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
||||
|
@ -138,10 +142,10 @@ private:
|
|||
ScriptEngine* _entitiesScriptEngine;
|
||||
ScriptEngine* _sandboxScriptEngine;
|
||||
|
||||
QScriptValue loadEntityScript(EntityItem* entity);
|
||||
QScriptValue loadEntityScript(const EntityItemID& entityItemID);
|
||||
QScriptValue loadEntityScript(EntityItem* entity, bool isPreload = false);
|
||||
QScriptValue loadEntityScript(const EntityItemID& entityItemID, bool isPreload = false);
|
||||
QScriptValue getPreviouslyLoadedEntityScript(const EntityItemID& entityItemID);
|
||||
QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL);
|
||||
QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& url);
|
||||
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID);
|
||||
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent);
|
||||
|
||||
|
@ -157,6 +161,8 @@ private:
|
|||
bool _dontDoPrecisionPicking;
|
||||
|
||||
bool _shuttingDown = false;
|
||||
|
||||
QMultiMap<QUrl, EntityItemID> _waitingOnPreload;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -266,6 +266,26 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
|
|||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::setCollisionModelURL(const QString& url) {
|
||||
ModelEntityItem::setCollisionModelURL(url);
|
||||
if (_model) {
|
||||
_model->setCollisionModelURL(QUrl(url));
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableModelEntityItem::hasCollisionModel() const {
|
||||
if (_model) {
|
||||
return ! _model->getCollisionURL().isEmpty();
|
||||
} else {
|
||||
return !_collisionModelURL.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
const QString& RenderableModelEntityItem::getCollisionModelURL() const {
|
||||
// assert (!_model || _collisionModelURL == _model->getCollisionURL().toString());
|
||||
return _collisionModelURL;
|
||||
}
|
||||
|
||||
bool RenderableModelEntityItem::isReadyToComputeShape() {
|
||||
|
||||
if (!_model) {
|
||||
|
@ -294,13 +314,103 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry();
|
||||
const FBXGeometry& fbxGeometry = collisionNetworkGeometry->getFBXGeometry();
|
||||
|
||||
AABox aaBox;
|
||||
_points.clear();
|
||||
unsigned int i = 0;
|
||||
|
||||
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
|
||||
// to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case.
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
_points << mesh.vertices;
|
||||
// each meshPart is a convex hull
|
||||
foreach (const FBXMeshPart &meshPart, mesh.parts) {
|
||||
QVector<glm::vec3> pointsInPart;
|
||||
|
||||
// run through all the triangles and (uniquely) add each point to the hull
|
||||
unsigned int triangleCount = meshPart.triangleIndices.size() / 3;
|
||||
assert((unsigned int)meshPart.triangleIndices.size() == triangleCount*3);
|
||||
for (unsigned int j = 0; j < triangleCount; j++) {
|
||||
unsigned int p0Index = meshPart.triangleIndices[j*3];
|
||||
unsigned int p1Index = meshPart.triangleIndices[j*3+1];
|
||||
unsigned int p2Index = meshPart.triangleIndices[j*3+2];
|
||||
assert(p0Index < (unsigned int)mesh.vertices.size());
|
||||
assert(p1Index < (unsigned int)mesh.vertices.size());
|
||||
assert(p2Index < (unsigned int)mesh.vertices.size());
|
||||
glm::vec3 p0 = mesh.vertices[p0Index];
|
||||
glm::vec3 p1 = mesh.vertices[p1Index];
|
||||
glm::vec3 p2 = mesh.vertices[p2Index];
|
||||
aaBox += p0;
|
||||
aaBox += p1;
|
||||
aaBox += p2;
|
||||
if (!pointsInPart.contains(p0)) {
|
||||
pointsInPart << p0;
|
||||
}
|
||||
if (!pointsInPart.contains(p1)) {
|
||||
pointsInPart << p1;
|
||||
}
|
||||
if (!pointsInPart.contains(p2)) {
|
||||
pointsInPart << p2;
|
||||
}
|
||||
}
|
||||
|
||||
// run through all the quads and (uniquely) add each point to the hull
|
||||
unsigned int quadCount = meshPart.quadIndices.size() / 4;
|
||||
assert((unsigned int)meshPart.quadIndices.size() == quadCount*4);
|
||||
for (unsigned int j = 0; j < quadCount; j++) {
|
||||
unsigned int p0Index = meshPart.quadIndices[j*4];
|
||||
unsigned int p1Index = meshPart.quadIndices[j*4+1];
|
||||
unsigned int p2Index = meshPart.quadIndices[j*4+2];
|
||||
unsigned int p3Index = meshPart.quadIndices[j*4+3];
|
||||
assert(p0Index < (unsigned int)mesh.vertices.size());
|
||||
assert(p1Index < (unsigned int)mesh.vertices.size());
|
||||
assert(p2Index < (unsigned int)mesh.vertices.size());
|
||||
assert(p3Index < (unsigned int)mesh.vertices.size());
|
||||
glm::vec3 p0 = mesh.vertices[p0Index];
|
||||
glm::vec3 p1 = mesh.vertices[p1Index];
|
||||
glm::vec3 p2 = mesh.vertices[p2Index];
|
||||
glm::vec3 p3 = mesh.vertices[p3Index];
|
||||
aaBox += p0;
|
||||
aaBox += p1;
|
||||
aaBox += p2;
|
||||
aaBox += p3;
|
||||
if (!pointsInPart.contains(p0)) {
|
||||
pointsInPart << p0;
|
||||
}
|
||||
if (!pointsInPart.contains(p1)) {
|
||||
pointsInPart << p1;
|
||||
}
|
||||
if (!pointsInPart.contains(p2)) {
|
||||
pointsInPart << p2;
|
||||
}
|
||||
if (!pointsInPart.contains(p3)) {
|
||||
pointsInPart << p3;
|
||||
}
|
||||
}
|
||||
|
||||
// add next convex hull
|
||||
QVector<glm::vec3> newMeshPoints;
|
||||
_points << newMeshPoints;
|
||||
// add points to the new convex hull
|
||||
_points[i++] << pointsInPart;
|
||||
}
|
||||
}
|
||||
|
||||
info.setParams(getShapeType(), 0.5f * getDimensions(), _collisionModelURL);
|
||||
info.setConvexHull(_points);
|
||||
// make sure we aren't about to divide by zero
|
||||
glm::vec3 aaBoxDim = aaBox.getDimensions();
|
||||
aaBoxDim = glm::clamp(aaBoxDim, glm::vec3(FLT_EPSILON), aaBoxDim);
|
||||
|
||||
glm::vec3 scale = _dimensions / aaBoxDim;
|
||||
// multiply each point by scale before handing the point-set off to the physics engine
|
||||
for (int i = 0; i < _points.size(); i++) {
|
||||
for (int j = 0; j < _points[i].size(); j++) {
|
||||
// compensate for registraion
|
||||
_points[i][j] += _model->getOffset();
|
||||
// scale so the collision points match the model points
|
||||
_points[i][j] *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
info.setParams(getShapeType(), _dimensions, _collisionModelURL);
|
||||
info.setConvexHulls(_points);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,7 +418,9 @@ ShapeType RenderableModelEntityItem::getShapeType() const {
|
|||
// XXX make hull an option in edit.js ?
|
||||
if (!_model || _model->getCollisionURL().isEmpty()) {
|
||||
return _shapeType;
|
||||
} else {
|
||||
} else if (_points.size() == 1) {
|
||||
return SHAPE_TYPE_CONVEX_HULL;
|
||||
} else {
|
||||
return SHAPE_TYPE_COMPOUND;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ public:
|
|||
|
||||
bool needsToCallUpdate() const;
|
||||
|
||||
virtual void setCollisionModelURL(const QString& url);
|
||||
virtual bool hasCollisionModel() const;
|
||||
virtual const QString& getCollisionModelURL() const;
|
||||
|
||||
bool isReadyToComputeShape();
|
||||
void computeShapeInfo(ShapeInfo& info);
|
||||
ShapeType getShapeType() const;
|
||||
|
@ -66,7 +70,7 @@ private:
|
|||
QString _currentTextures;
|
||||
QStringList _originalTextures;
|
||||
bool _originalTexturesRead;
|
||||
QVector<glm::vec3> _points;
|
||||
QVector<QVector<glm::vec3>> _points;
|
||||
};
|
||||
|
||||
#endif // hifi_RenderableModelEntityItem_h
|
||||
|
|
|
@ -57,7 +57,7 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
|
|||
_collisionsWillMove = ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE;
|
||||
_locked = ENTITY_ITEM_DEFAULT_LOCKED;
|
||||
_userData = ENTITY_ITEM_DEFAULT_USER_DATA;
|
||||
_attribution = ENTITY_ITEM_DEFAULT_ATTRIBUTION;
|
||||
_marketplaceID = ENTITY_ITEM_DEFAULT_MARKETPLACE_ID;
|
||||
}
|
||||
|
||||
EntityItem::EntityItem(const EntityItemID& entityItemID) {
|
||||
|
@ -117,7 +117,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
requestedProperties += PROP_COLLISIONS_WILL_MOVE;
|
||||
requestedProperties += PROP_LOCKED;
|
||||
requestedProperties += PROP_USER_DATA;
|
||||
requestedProperties += PROP_ATTRIBUTION;
|
||||
requestedProperties += PROP_MARKETPLACE_ID;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
@ -240,7 +240,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, appendValue, getCollisionsWillMove());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LOCKED, appendValue, getLocked());
|
||||
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, appendValue, getUserData());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ATTRIBUTION, appendValue, getAttribution());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, appendValue, getMarketplaceID());
|
||||
|
||||
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
|
||||
requestedProperties,
|
||||
|
@ -555,8 +555,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, _locked);
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA, setUserData);
|
||||
|
||||
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_ATTRIBUTION) {
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_ATTRIBUTION, setAttribution);
|
||||
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) {
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID);
|
||||
}
|
||||
|
||||
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
|
||||
|
@ -568,8 +568,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
// by doing this parsing here... but it's not likely going to fully recover the content.
|
||||
//
|
||||
// TODO: Remove this conde once we've sufficiently migrated content past this damaged version
|
||||
if (args.bitstreamVersion == VERSION_ENTITIES_HAS_ATTRIBUTION_DAMAGED) {
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_ATTRIBUTION, setAttribution);
|
||||
if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) {
|
||||
READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID);
|
||||
}
|
||||
|
||||
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) {
|
||||
|
@ -838,7 +838,7 @@ EntityItemProperties EntityItem::getProperties() const {
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(attribution, getAttribution);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID);
|
||||
|
||||
properties._defaultSettings = false;
|
||||
|
||||
|
@ -867,7 +867,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(attribution, setAttribution);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID);
|
||||
|
||||
if (somethingChanged) {
|
||||
somethingChangedNotification(); // notify derived classes that something has changed
|
||||
|
|
|
@ -251,8 +251,8 @@ public:
|
|||
const QString& getUserData() const { return _userData; }
|
||||
void setUserData(const QString& value) { _userData = value; }
|
||||
|
||||
const QString& getAttribution() const { return _attribution; }
|
||||
void setAttribution(const QString& value) { _attribution = value; }
|
||||
const QString& getMarketplaceID() const { return _marketplaceID; }
|
||||
void setMarketplaceID(const QString& value) { _marketplaceID = value; }
|
||||
|
||||
// TODO: get rid of users of getRadius()...
|
||||
float getRadius() const;
|
||||
|
@ -342,7 +342,7 @@ protected:
|
|||
bool _collisionsWillMove;
|
||||
bool _locked;
|
||||
QString _userData;
|
||||
QString _attribution;
|
||||
QString _marketplaceID;
|
||||
|
||||
// NOTE: Damping is applied like this: v *= pow(1 - damping, dt)
|
||||
//
|
||||
|
|
|
@ -70,7 +70,7 @@ EntityItemProperties::EntityItemProperties() :
|
|||
CONSTRUCT_PROPERTY(emitStrength, ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH),
|
||||
CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY),
|
||||
CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS),
|
||||
CONSTRUCT_PROPERTY(attribution, ENTITY_ITEM_DEFAULT_ATTRIBUTION),
|
||||
CONSTRUCT_PROPERTY(marketplaceID, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID),
|
||||
|
||||
_id(UNKNOWN_ENTITY_ID),
|
||||
_idSet(false),
|
||||
|
@ -260,7 +260,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_EMIT_STRENGTH, emitStrength);
|
||||
CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity);
|
||||
CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius);
|
||||
CHECK_PROPERTY_CHANGE(PROP_ATTRIBUTION, attribution);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID);
|
||||
|
||||
return changedProperties;
|
||||
}
|
||||
|
@ -323,7 +323,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(emitStrength);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(attribution);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(marketplaceID);
|
||||
|
||||
// Sitting properties support
|
||||
QScriptValue sittingPoints = engine->newObject();
|
||||
|
@ -405,7 +405,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitStrength, setEmitStrength);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localGravity, setLocalGravity);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(particleRadius, setParticleRadius);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(attribution, setAttribution);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(marketplaceID, setMarketplaceID);
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
@ -591,7 +591,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, properties.getParticleRadius());
|
||||
}
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_ATTRIBUTION, appendValue, properties.getAttribution());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, appendValue, properties.getMarketplaceID());
|
||||
}
|
||||
if (propertyCount > 0) {
|
||||
int endOfEntityItemData = packetData->getUncompressedByteOffset();
|
||||
|
@ -822,7 +822,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ATTRIBUTION, setAttribution);
|
||||
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MARKETPLACE_ID, setMarketplaceID);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
@ -905,7 +905,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
_localGravityChanged = true;
|
||||
_particleRadiusChanged = true;
|
||||
|
||||
_attributionChanged = true;
|
||||
_marketplaceIDChanged = true;
|
||||
}
|
||||
|
||||
/// The maximum bounding cube for the entity, independent of it's rotation.
|
||||
|
|
|
@ -94,11 +94,11 @@ enum EntityPropertyList {
|
|||
PROP_PARTICLE_RADIUS,
|
||||
|
||||
PROP_COLLISION_MODEL_URL,
|
||||
PROP_ATTRIBUTION,
|
||||
PROP_MARKETPLACE_ID,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties ABOVE this line and then modify PROP_LAST_ITEM below
|
||||
PROP_LAST_ITEM = PROP_ATTRIBUTION,
|
||||
PROP_LAST_ITEM = PROP_MARKETPLACE_ID,
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
@ -205,7 +205,7 @@ public:
|
|||
DEFINE_PROPERTY(PROP_EMIT_STRENGTH, EmitStrength, emitStrength, float);
|
||||
DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float);
|
||||
DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float);
|
||||
DEFINE_PROPERTY_REF(PROP_ATTRIBUTION, Attribution, attribution, QString);
|
||||
DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString);
|
||||
|
||||
public:
|
||||
float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); }
|
||||
|
@ -333,7 +333,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
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_PROPERTY_IF_CHANGED(debug, properties, Attribution, attribution, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, MarketplaceID, marketplaceID, "");
|
||||
|
||||
debug << " last edited:" << properties.getLastEdited() << "\n";
|
||||
debug << " edited ago:" << properties.getEditedAgo() << "\n";
|
||||
|
|
|
@ -22,7 +22,7 @@ const glm::vec3 ENTITY_ITEM_ZERO_VEC3(0.0f);
|
|||
|
||||
const bool ENTITY_ITEM_DEFAULT_LOCKED = false;
|
||||
const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString("");
|
||||
const QString ENTITY_ITEM_DEFAULT_ATTRIBUTION = QString("");
|
||||
const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString("");
|
||||
|
||||
const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f;
|
||||
const float ENTITY_ITEM_DEFAULT_GLOW_LEVEL = 0.0f;
|
||||
|
|
|
@ -95,7 +95,6 @@ public:
|
|||
|
||||
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false);
|
||||
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = false);
|
||||
void removeEntityFromSimulation(EntityItem* entity);
|
||||
|
||||
/// \param position point of query in world-frame (meters)
|
||||
/// \param targetRadius radius of query (meters)
|
||||
|
|
|
@ -281,6 +281,13 @@ void ModelEntityItem::updateShapeType(ShapeType type) {
|
|||
}
|
||||
}
|
||||
|
||||
void ModelEntityItem::setCollisionModelURL(const QString& url) {
|
||||
if (_collisionModelURL != url) {
|
||||
_collisionModelURL = url;
|
||||
_dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS;
|
||||
}
|
||||
}
|
||||
|
||||
void ModelEntityItem::setAnimationURL(const QString& url) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
|
||||
_animationURL = url;
|
||||
|
|
|
@ -57,13 +57,13 @@ 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(); }
|
||||
virtual 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; }
|
||||
virtual const QString& getCollisionModelURL() const { return _collisionModelURL; }
|
||||
|
||||
bool hasAnimation() const { return !_animationURL.isEmpty(); }
|
||||
static const QString DEFAULT_ANIMATION_URL;
|
||||
|
@ -78,7 +78,7 @@ public:
|
|||
|
||||
// model related properties
|
||||
void setModelURL(const QString& url) { _modelURL = url; }
|
||||
void setCollisionModelURL(const QString& url) { _collisionModelURL = url; }
|
||||
virtual void setCollisionModelURL(const QString& url);
|
||||
void setAnimationURL(const QString& url);
|
||||
static const float DEFAULT_ANIMATION_FRAME_INDEX;
|
||||
void setAnimationFrameIndex(float value);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include "FSTReader.h"
|
||||
|
||||
QVariantHash parseMapping(QIODevice* device) {
|
||||
QVariantHash FSTReader::parseMapping(QIODevice* device) {
|
||||
QVariantHash properties;
|
||||
|
||||
QByteArray line;
|
||||
|
@ -48,13 +48,13 @@ QVariantHash parseMapping(QIODevice* device) {
|
|||
return properties;
|
||||
}
|
||||
|
||||
QVariantHash readMapping(const QByteArray& data) {
|
||||
QVariantHash FSTReader::readMapping(const QByteArray& data) {
|
||||
QBuffer buffer(const_cast<QByteArray*>(&data));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
return parseMapping(&buffer);
|
||||
return FSTReader::parseMapping(&buffer);
|
||||
}
|
||||
|
||||
void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
|
||||
void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
|
||||
QByteArray key = it.key().toUtf8() + " = ";
|
||||
QVariantHash hashValue = it.value().toHash();
|
||||
if (hashValue.isEmpty()) {
|
||||
|
@ -76,8 +76,8 @@ void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
|
|||
}
|
||||
}
|
||||
|
||||
QByteArray writeMapping(const QVariantHash& mapping) {
|
||||
static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << SCALE_FIELD << FILENAME_FIELD
|
||||
QByteArray FSTReader::writeMapping(const QVariantHash& mapping) {
|
||||
static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD
|
||||
<< TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD
|
||||
<< BLENDSHAPE_FIELD << JOINT_INDEX_FIELD;
|
||||
QBuffer buffer;
|
||||
|
@ -96,4 +96,76 @@ QByteArray writeMapping(const QVariantHash& mapping) {
|
|||
}
|
||||
}
|
||||
return buffer.data();
|
||||
}
|
||||
}
|
||||
|
||||
QHash<FSTReader::ModelType, QString> FSTReader::_typesToNames;
|
||||
QString FSTReader::getNameFromType(ModelType modelType) {
|
||||
if (_typesToNames.size() == 0) {
|
||||
_typesToNames[ENTITY_MODEL] = "entity";
|
||||
_typesToNames[HEAD_MODEL] = "head";
|
||||
_typesToNames[BODY_ONLY_MODEL] = "body";
|
||||
_typesToNames[HEAD_AND_BODY_MODEL] = "body+head";
|
||||
_typesToNames[ATTACHMENT_MODEL] = "attachment";
|
||||
}
|
||||
return _typesToNames[modelType];
|
||||
}
|
||||
|
||||
QHash<QString, FSTReader::ModelType> FSTReader::_namesToTypes;
|
||||
FSTReader::ModelType FSTReader::getTypeFromName(const QString& name) {
|
||||
if (_namesToTypes.size() == 0) {
|
||||
_namesToTypes["entity"] = ENTITY_MODEL;
|
||||
_namesToTypes["head"] = HEAD_MODEL ;
|
||||
_namesToTypes["body"] = BODY_ONLY_MODEL;
|
||||
_namesToTypes["body+head"] = HEAD_AND_BODY_MODEL;
|
||||
_namesToTypes["attachment"] = ATTACHMENT_MODEL;
|
||||
}
|
||||
return _namesToTypes[name];
|
||||
}
|
||||
|
||||
FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) {
|
||||
|
||||
QVariantHash joints;
|
||||
|
||||
if (mapping.contains("joint") && mapping["joint"].type() == QVariant::Hash) {
|
||||
joints = mapping["joint"].toHash();
|
||||
}
|
||||
|
||||
// if the mapping includes the type hint... then we trust the mapping
|
||||
if (mapping.contains(TYPE_FIELD)) {
|
||||
return FSTReader::getTypeFromName(mapping[TYPE_FIELD].toString());
|
||||
}
|
||||
|
||||
// check for blendshapes
|
||||
bool hasBlendshapes = mapping.contains(BLENDSHAPE_FIELD);
|
||||
|
||||
// a Head needs to have these minimum fields...
|
||||
//joint = jointEyeLeft = EyeL = 1
|
||||
//joint = jointEyeRight = EyeR = 1
|
||||
//joint = jointNeck = Head = 1
|
||||
bool hasHeadMinimum = joints.contains("jointNeck") && joints.contains("jointEyeLeft") && joints.contains("jointEyeRight");
|
||||
|
||||
// a Body needs to have these minimum fields...
|
||||
//joint = jointRoot = Hips
|
||||
//joint = jointLean = Spine
|
||||
//joint = jointNeck = Neck
|
||||
//joint = jointHead = HeadTop_End
|
||||
|
||||
bool hasBodyMinimumJoints = joints.contains("jointRoot") && joints.contains("jointLean") && joints.contains("jointNeck")
|
||||
&& joints.contains("jointHead");
|
||||
|
||||
bool isLikelyHead = hasBlendshapes || hasHeadMinimum;
|
||||
|
||||
if (isLikelyHead && hasBodyMinimumJoints) {
|
||||
return HEAD_AND_BODY_MODEL;
|
||||
}
|
||||
|
||||
if (isLikelyHead) {
|
||||
return HEAD_MODEL;
|
||||
}
|
||||
|
||||
if (hasBodyMinimumJoints) {
|
||||
return BODY_ONLY_MODEL;
|
||||
}
|
||||
|
||||
return ENTITY_MODEL;
|
||||
}
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
#ifndef hifi_FSTReader_h
|
||||
#define hifi_FSTReader_h
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QVariantHash>
|
||||
|
||||
static const QString NAME_FIELD = "name";
|
||||
static const QString TYPE_FIELD = "type";
|
||||
static const QString FILENAME_FIELD = "filename";
|
||||
static const QString TEXDIR_FIELD = "texdir";
|
||||
static const QString LOD_FIELD = "lod";
|
||||
|
@ -27,10 +29,35 @@ static const QString JOINT_FIELD = "joint";
|
|||
static const QString FREE_JOINT_FIELD = "freeJoint";
|
||||
static const QString BLENDSHAPE_FIELD = "bs";
|
||||
|
||||
/// Reads an FST mapping from the supplied data.
|
||||
QVariantHash readMapping(const QByteArray& data);
|
||||
class FSTReader {
|
||||
public:
|
||||
|
||||
/// Writes an FST mapping to a byte array.
|
||||
QByteArray writeMapping(const QVariantHash& mapping);
|
||||
enum ModelType {
|
||||
ENTITY_MODEL,
|
||||
HEAD_MODEL,
|
||||
BODY_ONLY_MODEL,
|
||||
HEAD_AND_BODY_MODEL,
|
||||
ATTACHMENT_MODEL
|
||||
};
|
||||
|
||||
/// Reads an FST mapping from the supplied data.
|
||||
static QVariantHash readMapping(const QByteArray& data);
|
||||
|
||||
/// Writes an FST mapping to a byte array.
|
||||
static QByteArray writeMapping(const QVariantHash& mapping);
|
||||
|
||||
/// Predicts the type of model by examining the mapping
|
||||
static ModelType predictModelType(const QVariantHash& mapping);
|
||||
|
||||
static QString getNameFromType(ModelType modelType);
|
||||
static FSTReader::ModelType getTypeFromName(const QString& name);
|
||||
|
||||
private:
|
||||
static void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it);
|
||||
static QVariantHash parseMapping(QIODevice* device);
|
||||
|
||||
static QHash<FSTReader::ModelType, QString> _typesToNames;
|
||||
static QHash<QString, FSTReader::ModelType> _namesToTypes;
|
||||
};
|
||||
|
||||
#endif // hifi_FSTReader_h
|
|
@ -249,7 +249,20 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
|
|||
} else if (indices.count() == 4) {
|
||||
meshPart.quadIndices << indices;
|
||||
} else {
|
||||
qDebug() << "no support for more than 4 vertices on a face in OBJ files";
|
||||
// some obj writers (maya) will write a face with lots of points.
|
||||
for (int i = 1; i < indices.count() - 1; i++) {
|
||||
// break the face into triangles
|
||||
meshPart.triangleIndices.append(indices[0]);
|
||||
meshPart.triangleIndices.append(indices[i]);
|
||||
meshPart.triangleIndices.append(indices[i+1]);
|
||||
}
|
||||
if (indices.count() == normalIndices.count()) {
|
||||
for (int i = 1; i < normalIndices.count() - 1; i++) {
|
||||
faceNormalIndexes.append(normalIndices[0]);
|
||||
faceNormalIndexes.append(normalIndices[i]);
|
||||
faceNormalIndexes.append(normalIndices[i+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// something we don't (yet) care about
|
||||
|
|
|
@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
return 1;
|
||||
case PacketTypeEntityAddOrEdit:
|
||||
case PacketTypeEntityData:
|
||||
return VERSION_ENTITIES_HAS_ATTRIBUTION;
|
||||
return VERSION_ENTITIES_HAS_MARKETPLACE_ID;
|
||||
case PacketTypeEntityErase:
|
||||
return 2;
|
||||
case PacketTypeAudioStreamStats:
|
||||
|
|
|
@ -132,8 +132,8 @@ const PacketVersion VERSION_ENTITIES_LIGHT_HAS_INTENSITY_AND_COLOR_PROPERTIES =
|
|||
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_ENTITIES_HAS_ATTRIBUTION_DAMAGED = 13;
|
||||
const PacketVersion VERSION_ENTITIES_HAS_ATTRIBUTION = 14;
|
||||
const PacketVersion VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED = 13;
|
||||
const PacketVersion VERSION_ENTITIES_HAS_MARKETPLACE_ID = 14;
|
||||
const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1;
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <QTimer>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "NetworkAccessManager.h"
|
||||
#include "ResourceCache.h"
|
||||
|
@ -48,32 +49,40 @@ void ResourceCache::refresh(const QUrl& url) {
|
|||
}
|
||||
}
|
||||
|
||||
void ResourceCache::getResourceAsynchronously(const QUrl& url) {
|
||||
qDebug() << "ResourceCache::getResourceAsynchronously" << url.toString();
|
||||
_resourcesToBeGottenLock.lockForWrite();
|
||||
_resourcesToBeGotten.enqueue(QUrl(url));
|
||||
_resourcesToBeGottenLock.unlock();
|
||||
}
|
||||
|
||||
void ResourceCache::checkAsynchronousGets() {
|
||||
assert(QThread::currentThread() == thread());
|
||||
if (!_resourcesToBeGotten.isEmpty()) {
|
||||
_resourcesToBeGottenLock.lockForWrite();
|
||||
QUrl url = _resourcesToBeGotten.dequeue();
|
||||
_resourcesToBeGottenLock.unlock();
|
||||
getResource(url);
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback,
|
||||
bool delayLoad, void* extra, bool block) {
|
||||
bool delayLoad, void* extra) {
|
||||
QSharedPointer<Resource> resource = _resources.value(url);
|
||||
if (!resource.isNull()) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
// 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);
|
||||
}
|
||||
assert(delayLoad);
|
||||
getResourceAsynchronously(url);
|
||||
return QSharedPointer<Resource>();
|
||||
}
|
||||
|
||||
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) {
|
||||
return getResource(fallback, QUrl(), delayLoad);
|
||||
}
|
||||
QSharedPointer<Resource> resource = _resources.value(url);
|
||||
|
||||
if (resource.isNull()) {
|
||||
resource = createResource(url, fallback.isValid() ?
|
||||
getResource(fallback, QUrl(), true) : QSharedPointer<Resource>(), delayLoad, extra);
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include <QSharedPointer>
|
||||
#include <QUrl>
|
||||
#include <QWeakPointer>
|
||||
#include <QReadWriteLock>
|
||||
#include <QQueue>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
|
@ -79,6 +81,9 @@ public:
|
|||
|
||||
void refresh(const QUrl& url);
|
||||
|
||||
public slots:
|
||||
void checkAsynchronousGets();
|
||||
|
||||
protected:
|
||||
qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE;
|
||||
qint64 _unusedResourcesSize = 0;
|
||||
|
@ -89,7 +94,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 block = true);
|
||||
bool delayLoad = false, void* extra = NULL);
|
||||
|
||||
/// Creates a new resource.
|
||||
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
||||
|
@ -109,6 +114,11 @@ private:
|
|||
int _lastLRUKey = 0;
|
||||
|
||||
static int _requestLimit;
|
||||
|
||||
void getResourceAsynchronously(const QUrl& url);
|
||||
QReadWriteLock _resourcesToBeGottenLock;
|
||||
QQueue<QUrl> _resourcesToBeGotten;
|
||||
|
||||
};
|
||||
|
||||
/// Base class for resources.
|
||||
|
|
75
libraries/physics/src/CharacterController.cpp
Normal file → Executable file
75
libraries/physics/src/CharacterController.cpp
Normal file → Executable file
|
@ -235,10 +235,18 @@ CharacterController::CharacterController(AvatarData* avatarData) {
|
|||
_jumpToHoverStart = 0;
|
||||
setMaxSlope(btRadians(45.0f));
|
||||
_lastStepUp = 0.0f;
|
||||
_pendingFlags = 0;
|
||||
|
||||
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
|
||||
updateShapeIfNecessary();
|
||||
}
|
||||
|
||||
CharacterController::~CharacterController() {
|
||||
delete _ghostObject;
|
||||
_ghostObject = NULL;
|
||||
delete _convexShape;
|
||||
_convexShape = NULL;
|
||||
// make sure you remove this Character from its DynamicsWorld before reaching this spot
|
||||
assert(_dynamicsWorld == NULL);
|
||||
}
|
||||
|
||||
btPairCachingGhostObject* CharacterController::getGhostObject() {
|
||||
|
@ -266,7 +274,6 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
|
|||
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
|
||||
|
||||
_currentPosition = _ghostObject->getWorldTransform().getOrigin();
|
||||
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
|
||||
|
||||
btVector3 currentPosition = _currentPosition;
|
||||
|
||||
|
@ -300,7 +307,7 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
|
|||
btVector3 normal = pt.m_normalWorldOnB;
|
||||
normal *= directionSign; // always points from object to character
|
||||
|
||||
btScalar normalDotUp = normal.dot(up);
|
||||
btScalar normalDotUp = normal.dot(_currentUp);
|
||||
if (normalDotUp < _maxSlopeCosine) {
|
||||
// this contact has a non-vertical normal... might need to ignored
|
||||
btVector3 collisionPoint;
|
||||
|
@ -311,9 +318,9 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
|
|||
}
|
||||
|
||||
// we do math in frame where character base is origin
|
||||
btVector3 characterBase = currentPosition - (_radius + _halfHeight) * up;
|
||||
btVector3 characterBase = currentPosition - (_radius + _halfHeight) * _currentUp;
|
||||
collisionPoint -= characterBase;
|
||||
btScalar collisionHeight = collisionPoint.dot(up);
|
||||
btScalar collisionHeight = collisionPoint.dot(_currentUp);
|
||||
|
||||
if (collisionHeight < _lastStepUp) {
|
||||
// This contact is below the lastStepUp, so we ignore it for penetration resolution,
|
||||
|
@ -349,11 +356,10 @@ void CharacterController::scanDown(btCollisionWorld* world) {
|
|||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
|
||||
btVector3 start = _currentPosition;
|
||||
const btScalar MAX_SCAN_HEIGHT = 20.0f + _halfHeight + _radius; // closest possible floor for disabling hover
|
||||
const btScalar MIN_HOVER_HEIGHT = 3.0f + _halfHeight + _radius; // distance to floor for enabling hover
|
||||
btVector3 end = start - MAX_SCAN_HEIGHT * up;
|
||||
btVector3 end = start - MAX_SCAN_HEIGHT * _currentUp;
|
||||
|
||||
world->rayTest(start, end, callback);
|
||||
if (!callback.hasHit()) {
|
||||
|
@ -369,15 +375,14 @@ void CharacterController::stepUp(btCollisionWorld* world) {
|
|||
// compute start and end
|
||||
btTransform start, end;
|
||||
start.setIdentity();
|
||||
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
|
||||
start.setOrigin(_currentPosition + up * (_convexShape->getMargin() + _addedMargin));
|
||||
start.setOrigin(_currentPosition + _currentUp * (_convexShape->getMargin() + _addedMargin));
|
||||
|
||||
_targetPosition = _currentPosition + up * _stepUpHeight;
|
||||
_targetPosition = _currentPosition + _currentUp * _stepUpHeight;
|
||||
end.setIdentity();
|
||||
end.setOrigin(_targetPosition);
|
||||
|
||||
// sweep up
|
||||
btVector3 sweepDirNegative = - up;
|
||||
btVector3 sweepDirNegative = - _currentUp;
|
||||
btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.7071));
|
||||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
@ -389,7 +394,7 @@ void CharacterController::stepUp(btCollisionWorld* world) {
|
|||
_verticalOffset = 0.0f;
|
||||
|
||||
// Only modify the position if the hit was a slope and not a wall or ceiling.
|
||||
if (callback.m_hitNormalWorld.dot(up) > 0.0f) {
|
||||
if (callback.m_hitNormalWorld.dot(_currentUp) > 0.0f) {
|
||||
_lastStepUp = _stepUpHeight * callback.m_closestHitFraction;
|
||||
_currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction);
|
||||
} else {
|
||||
|
@ -461,8 +466,8 @@ void CharacterController::stepForward(btCollisionWorld* collisionWorld, const bt
|
|||
// sweep forward
|
||||
btVector3 sweepDirNegative(_currentPosition - _targetPosition);
|
||||
btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.0));
|
||||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
_ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
||||
|
||||
if (callback.hasHit()) {
|
||||
|
@ -494,17 +499,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt
|
|||
// reach of the character's feet.
|
||||
|
||||
// first sweep for ledge
|
||||
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
|
||||
btVector3 step = (_verticalVelocity * dt - _lastStepUp) * up;
|
||||
btVector3 step = (_verticalVelocity * dt - _lastStepUp) * _currentUp;
|
||||
|
||||
StepDownConvexResultCallback callback(_ghostObject,
|
||||
up,
|
||||
_currentUp,
|
||||
_currentPosition, step,
|
||||
_walkDirection,
|
||||
_maxSlopeCosine,
|
||||
_radius, _halfHeight);
|
||||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
btTransform start, end;
|
||||
start.setIdentity();
|
||||
|
@ -524,16 +528,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt
|
|||
_isOnGround = true;
|
||||
} else if (!_isJumping) {
|
||||
// sweep again for floor within downStep threshold
|
||||
step = -_stepDownHeight * up;
|
||||
step = -_stepDownHeight * _currentUp;
|
||||
StepDownConvexResultCallback callback2 (_ghostObject,
|
||||
up,
|
||||
_currentUp,
|
||||
_currentPosition, step,
|
||||
_walkDirection,
|
||||
_maxSlopeCosine,
|
||||
_radius, _halfHeight);
|
||||
|
||||
callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
callback2.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback2.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
_currentPosition = _targetPosition;
|
||||
_targetPosition = _currentPosition + step;
|
||||
|
@ -609,10 +613,10 @@ void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
|||
}
|
||||
}
|
||||
|
||||
// the CharacterController algorithm can only change the position,
|
||||
// so we don't bother to pull the rotation out of the transform
|
||||
const btTransform& transform = _ghostObject->getWorldTransform();
|
||||
_currentRotation = transform.getRotation();
|
||||
_currentPosition = transform.getOrigin();
|
||||
_targetPosition = _currentPosition;
|
||||
}
|
||||
|
||||
void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) {
|
||||
|
@ -786,14 +790,17 @@ void CharacterController::setEnabled(bool enabled) {
|
|||
|
||||
void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
if (_dynamicsWorld != world) {
|
||||
if (_dynamicsWorld) {
|
||||
_dynamicsWorld->removeCollisionObject(getGhostObject());
|
||||
_dynamicsWorld->removeAction(this);
|
||||
if (_dynamicsWorld) {
|
||||
if (_ghostObject) {
|
||||
_dynamicsWorld->removeCollisionObject(_ghostObject);
|
||||
_dynamicsWorld->removeAction(this);
|
||||
}
|
||||
_dynamicsWorld = NULL;
|
||||
}
|
||||
_dynamicsWorld = world;
|
||||
if (_dynamicsWorld) {
|
||||
if (world && _ghostObject) {
|
||||
_dynamicsWorld = world;
|
||||
_pendingFlags &= ~ PENDING_FLAG_JUMP;
|
||||
_dynamicsWorld->addCollisionObject(getGhostObject(),
|
||||
_dynamicsWorld->addCollisionObject(_ghostObject,
|
||||
btBroadphaseProxy::CharacterFilter,
|
||||
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
||||
_dynamicsWorld->addAction(this);
|
||||
|
@ -857,6 +864,7 @@ void CharacterController::updateShapeIfNecessary() {
|
|||
void CharacterController::preSimulation(btScalar timeStep) {
|
||||
if (_enabled && _dynamicsWorld) {
|
||||
glm::quat rotation = _avatarData->getOrientation();
|
||||
btVector3 _currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
|
||||
glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset;
|
||||
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
|
||||
|
||||
|
@ -876,7 +884,7 @@ void CharacterController::preSimulation(btScalar timeStep) {
|
|||
}
|
||||
|
||||
void CharacterController::postSimulation() {
|
||||
if (_enabled) {
|
||||
if (_enabled && _ghostObject) {
|
||||
const btTransform& avatarTransform = _ghostObject->getWorldTransform();
|
||||
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
|
||||
glm::vec3 position = bulletToGLM(avatarTransform.getOrigin());
|
||||
|
@ -884,8 +892,7 @@ void CharacterController::postSimulation() {
|
|||
// cap the velocity of the step so that the character doesn't POP! so hard on steps
|
||||
glm::vec3 finalStep = position - _lastPosition;
|
||||
btVector3 finalVelocity = _walkDirection;
|
||||
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
|
||||
finalVelocity += _verticalVelocity * up;
|
||||
finalVelocity += _verticalVelocity * _currentUp;
|
||||
const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec
|
||||
btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt;
|
||||
btScalar stepLength = glm::length(finalStep);
|
||||
|
|
|
@ -42,9 +42,22 @@ class btPairCachingGhostObject;
|
|||
ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface
|
||||
{
|
||||
protected:
|
||||
///this is the desired walk direction, set by the user
|
||||
btVector3 _walkDirection;
|
||||
btVector3 _normalizedDirection;
|
||||
|
||||
//some internal variables
|
||||
btVector3 _currentPosition;
|
||||
btVector3 _currentUp;
|
||||
btVector3 _targetPosition;
|
||||
glm::vec3 _lastPosition;
|
||||
btVector3 _floorNormal; // points from object to character
|
||||
|
||||
glm::vec3 _shapeLocalOffset;
|
||||
glm::vec3 _boxScale; // used to compute capsule shape
|
||||
|
||||
AvatarData* _avatarData = NULL;
|
||||
btPairCachingGhostObject* _ghostObject;
|
||||
btPairCachingGhostObject* _ghostObject = NULL;
|
||||
|
||||
btConvexShape* _convexShape;//is also in _ghostObject, but it needs to be convex, so we store it here to avoid upcast
|
||||
btScalar _radius;
|
||||
|
@ -64,22 +77,12 @@ protected:
|
|||
|
||||
btScalar _addedMargin;//@todo: remove this and fix the code
|
||||
|
||||
///this is the desired walk direction, set by the user
|
||||
btVector3 _walkDirection;
|
||||
btVector3 _normalizedDirection;
|
||||
|
||||
//some internal variables
|
||||
btVector3 _currentPosition;
|
||||
btQuaternion _currentRotation;
|
||||
btVector3 _targetPosition;
|
||||
glm::vec3 _lastPosition;
|
||||
btScalar _lastStepUp;
|
||||
|
||||
///keep track of the contact manifolds
|
||||
btManifoldArray _manifoldArray;
|
||||
|
||||
bool _touchingContact;
|
||||
btVector3 _floorNormal; // points from object to character
|
||||
|
||||
bool _enabled;
|
||||
bool _isOnGround;
|
||||
|
@ -90,9 +93,6 @@ protected:
|
|||
btScalar _stepDt;
|
||||
uint32_t _pendingFlags;
|
||||
|
||||
glm::vec3 _shapeLocalOffset;
|
||||
glm::vec3 _boxScale; // used to compute capsule shape
|
||||
|
||||
btDynamicsWorld* _dynamicsWorld = NULL;
|
||||
|
||||
btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal);
|
||||
|
|
|
@ -169,7 +169,9 @@ void EntityMotionState::updateObjectVelocities() {
|
|||
}
|
||||
|
||||
void EntityMotionState::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||
_entity->computeShapeInfo(shapeInfo);
|
||||
if (_entity->isReadyToComputeShape()) {
|
||||
_entity->computeShapeInfo(shapeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
float EntityMotionState::computeMass(const ShapeInfo& shapeInfo) const {
|
||||
|
|
|
@ -28,7 +28,16 @@ PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
|
|||
}
|
||||
|
||||
PhysicsEngine::~PhysicsEngine() {
|
||||
if (_characterController) {
|
||||
_characterController->setDynamicsWorld(NULL);
|
||||
}
|
||||
// TODO: delete engine components... if we ever plan to create more than one instance
|
||||
delete _collisionConfig;
|
||||
delete _collisionDispatcher;
|
||||
delete _broadphaseFilter;
|
||||
delete _constraintSolver;
|
||||
delete _dynamicsWorld;
|
||||
delete _ghostPairCallback;
|
||||
}
|
||||
|
||||
// begin EntitySimulation overrides
|
||||
|
@ -608,8 +617,14 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
|||
}
|
||||
|
||||
void PhysicsEngine::setCharacterController(CharacterController* character) {
|
||||
if (!_characterController) {
|
||||
if (_characterController != character) {
|
||||
lock();
|
||||
if (_characterController) {
|
||||
// remove the character from the DynamicsWorld immediately
|
||||
_characterController->setDynamicsWorld(NULL);
|
||||
_characterController = NULL;
|
||||
}
|
||||
// the character will be added to the DynamicsWorld later
|
||||
_characterController = character;
|
||||
unlock();
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) {
|
|||
case SHAPE_TYPE_CONVEX_HULL:
|
||||
bulletShapeType = CONVEX_HULL_SHAPE_PROXYTYPE;
|
||||
break;
|
||||
case SHAPE_TYPE_COMPOUND:
|
||||
bulletShapeType = COMPOUND_SHAPE_PROXYTYPE;
|
||||
break;
|
||||
}
|
||||
return bulletShapeType;
|
||||
}
|
||||
|
@ -48,6 +51,9 @@ int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) {
|
|||
case CONVEX_HULL_SHAPE_PROXYTYPE:
|
||||
shapeInfoType = SHAPE_TYPE_CONVEX_HULL;
|
||||
break;
|
||||
case COMPOUND_SHAPE_PROXYTYPE:
|
||||
shapeInfoType = SHAPE_TYPE_COMPOUND;
|
||||
break;
|
||||
}
|
||||
return shapeInfoType;
|
||||
}
|
||||
|
@ -70,12 +76,34 @@ void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInf
|
|||
const btConvexHullShape* convexHullShape = static_cast<const btConvexHullShape*>(shape);
|
||||
const int numPoints = convexHullShape->getNumPoints();
|
||||
const btVector3* btPoints = convexHullShape->getUnscaledPoints();
|
||||
QVector<glm::vec3> points;
|
||||
QVector<QVector<glm::vec3>> points;
|
||||
QVector<glm::vec3> childPoints;
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ());
|
||||
points << point;
|
||||
childPoints << point;
|
||||
}
|
||||
info.setConvexHull(points);
|
||||
points << childPoints;
|
||||
info.setConvexHulls(points);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_COMPOUND: {
|
||||
const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shape);
|
||||
const int numChildShapes = compoundShape->getNumChildShapes();
|
||||
QVector<QVector<glm::vec3>> points;
|
||||
for (int i = 0; i < numChildShapes; i ++) {
|
||||
const btCollisionShape* childShape = compoundShape->getChildShape(i);
|
||||
const btConvexHullShape* convexHullShape = static_cast<const btConvexHullShape*>(childShape);
|
||||
const int numPoints = convexHullShape->getNumPoints();
|
||||
const btVector3* btPoints = convexHullShape->getUnscaledPoints();
|
||||
|
||||
QVector<glm::vec3> childPoints;
|
||||
for (int j = 0; j < numPoints; j++) {
|
||||
glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ());
|
||||
childPoints << point;
|
||||
}
|
||||
points << childPoints;
|
||||
}
|
||||
info.setConvexHulls(points);
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
|
@ -108,12 +136,32 @@ btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) {
|
|||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CONVEX_HULL: {
|
||||
shape = new btConvexHullShape();
|
||||
const QVector<glm::vec3>& points = info.getPoints();
|
||||
foreach (glm::vec3 point, points) {
|
||||
auto hull = new btConvexHullShape();
|
||||
const QVector<QVector<glm::vec3>>& points = info.getPoints();
|
||||
foreach (glm::vec3 point, points[0]) {
|
||||
btVector3 btPoint(point[0], point[1], point[2]);
|
||||
static_cast<btConvexHullShape*>(shape)->addPoint(btPoint);
|
||||
hull->addPoint(btPoint, false);
|
||||
}
|
||||
hull->recalcLocalAabb();
|
||||
shape = hull;
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_COMPOUND: {
|
||||
auto compound = new btCompoundShape();
|
||||
const QVector<QVector<glm::vec3>>& points = info.getPoints();
|
||||
|
||||
btTransform trans;
|
||||
trans.setIdentity();
|
||||
foreach (QVector<glm::vec3> hullPoints, points) {
|
||||
auto hull = new btConvexHullShape();
|
||||
foreach (glm::vec3 point, hullPoints) {
|
||||
btVector3 btPoint(point[0], point[1], point[2]);
|
||||
hull->addPoint(btPoint, false);
|
||||
}
|
||||
hull->recalcLocalAabb();
|
||||
compound->addChildShape (trans, hull);
|
||||
}
|
||||
shape = compound;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
// translates between ShapeInfo and btShape
|
||||
|
||||
namespace ShapeInfoUtil {
|
||||
|
||||
// XXX is collectInfoFromShape no longer strictly needed?
|
||||
void collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info);
|
||||
|
||||
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include "ShapeInfoUtil.h"
|
||||
|
@ -35,6 +37,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
|
|||
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
|
||||
const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e4f; // 100 m cube
|
||||
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED || diagonal > MAX_SHAPE_DIAGONAL_SQUARED) {
|
||||
// qDebug() << "ShapeManager::getShape -- not making shape due to size" << diagonal;
|
||||
return NULL;
|
||||
}
|
||||
DoubleHashKey key = info.getHash();
|
||||
|
@ -100,6 +103,18 @@ void ShapeManager::collectGarbage() {
|
|||
DoubleHashKey& key = _pendingGarbage[i];
|
||||
ShapeReference* shapeRef = _shapeMap.find(key);
|
||||
if (shapeRef && shapeRef->refCount == 0) {
|
||||
// if the shape we're about to delete is compound, delete the children first.
|
||||
auto shapeType = ShapeInfoUtil::fromBulletShapeType(shapeRef->shape->getShapeType());
|
||||
if (shapeType == SHAPE_TYPE_COMPOUND) {
|
||||
const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shapeRef->shape);
|
||||
const int numChildShapes = compoundShape->getNumChildShapes();
|
||||
QVector<QVector<glm::vec3>> points;
|
||||
for (int i = 0; i < numChildShapes; i ++) {
|
||||
const btCollisionShape* childShape = compoundShape->getChildShape(i);
|
||||
delete childShape;
|
||||
}
|
||||
}
|
||||
|
||||
delete shapeRef->shape;
|
||||
_shapeMap.remove(key);
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radi
|
|||
void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color,
|
||||
float intensity, const glm::quat& orientation, float exponent, float cutoff) {
|
||||
|
||||
int lightID = _pointLights.size() + _spotLights.size() + _globalLights.size();
|
||||
unsigned int lightID = _pointLights.size() + _spotLights.size() + _globalLights.size();
|
||||
if (lightID >= _allocatedLights.size()) {
|
||||
_allocatedLights.push_back(model::LightPointer(new model::Light()));
|
||||
}
|
||||
|
|
|
@ -1771,8 +1771,8 @@ void GeometryCache::renderLine(const glm::vec2& p1, const glm::vec2& p2,
|
|||
}
|
||||
|
||||
|
||||
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad, bool block) {
|
||||
return getResource(url, fallback, delayLoad, NULL, block).staticCast<NetworkGeometry>();
|
||||
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) {
|
||||
return getResource(url, fallback, delayLoad, NULL).staticCast<NetworkGeometry>();
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url,
|
||||
|
@ -2132,7 +2132,7 @@ void NetworkGeometry::downloadFinished(QNetworkReply* reply) {
|
|||
QUrl url = reply->url();
|
||||
if (url.path().toLower().endsWith(".fst")) {
|
||||
// it's a mapping file; parse it and get the mesh filename
|
||||
_mapping = readMapping(reply->readAll());
|
||||
_mapping = FSTReader::readMapping(reply->readAll());
|
||||
reply->deleteLater();
|
||||
QString filename = _mapping.value("filename").toString();
|
||||
if (filename.isNull()) {
|
||||
|
|
|
@ -203,8 +203,7 @@ 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, bool block = true);
|
||||
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -325,6 +325,8 @@ void Model::init() {
|
|||
_skinTranslucentProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelVertex, modelTranslucentPixel));
|
||||
makeResult = gpu::Shader::makeProgram(*_skinTranslucentProgram, slotBindings);
|
||||
initSkinProgram(_skinTranslucentProgram, _skinTranslucentLocations);
|
||||
|
||||
(void) makeResult; // quiet compiler
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1032,12 +1034,22 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo
|
|||
}
|
||||
}
|
||||
|
||||
void Model::setCollisionModelURL(const QUrl& url, const QUrl& fallback, bool delayLoad) {
|
||||
|
||||
const QSharedPointer<NetworkGeometry> Model::getCollisionGeometry(bool delayLoad)
|
||||
{
|
||||
if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) {
|
||||
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(_collisionUrl, QUrl(), delayLoad);
|
||||
}
|
||||
|
||||
return _collisionGeometry;
|
||||
}
|
||||
|
||||
void Model::setCollisionModelURL(const QUrl& url) {
|
||||
if (_collisionUrl == url) {
|
||||
return;
|
||||
}
|
||||
_collisionUrl = url;
|
||||
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, fallback, delayLoad);
|
||||
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, QUrl(), true);
|
||||
}
|
||||
|
||||
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
||||
|
|
|
@ -109,7 +109,7 @@ public:
|
|||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
// Set the model to use for collisions
|
||||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
||||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
|
||||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||
|
||||
/// Sets the distance parameter used for LOD computations.
|
||||
|
@ -134,7 +134,7 @@ public:
|
|||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||
|
||||
/// Returns a reference to the shared collision geometry.
|
||||
const QSharedPointer<NetworkGeometry> getCollisionGeometry() {return _collisionGeometry; }
|
||||
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = true);
|
||||
|
||||
/// Returns the number of joint states in the model.
|
||||
int getJointStateCount() const { return _jointStates.size(); }
|
||||
|
|
|
@ -359,8 +359,12 @@ void Font::setupGL() {
|
|||
// FIXME there has to be a cleaner way of doing this
|
||||
QStringList Font::tokenizeForWrapping(const QString & str) const {
|
||||
QStringList result;
|
||||
foreach(const QString & token1, str.split(" ", QString::SkipEmptyParts)) {
|
||||
foreach(const QString & token1, str.split(" ")) {
|
||||
bool lineFeed = false;
|
||||
if (token1.isEmpty()) {
|
||||
result << token1;
|
||||
continue;
|
||||
}
|
||||
foreach(const QString & token2, token1.split("\n")) {
|
||||
if (lineFeed) {
|
||||
result << "\n";
|
||||
|
|
76
libraries/script-engine/src/ScriptCache.cpp
Normal file
76
libraries/script-engine/src/ScriptCache.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// ScriptCache.cpp
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015-03-30
|
||||
// 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 <QCoreApplication>
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "ScriptCache.h"
|
||||
|
||||
ScriptCache::ScriptCache(QObject* parent) {
|
||||
// nothing to do here...
|
||||
}
|
||||
|
||||
QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending) {
|
||||
QString scriptContents;
|
||||
if (_scriptCache.contains(url)) {
|
||||
qDebug() << "Found script in cache:" << url.toString();
|
||||
scriptContents = _scriptCache[url];
|
||||
scriptUser->scriptContentsAvailable(url, scriptContents);
|
||||
isPending = false;
|
||||
} else {
|
||||
isPending = true;
|
||||
bool alreadyWaiting = _scriptUsers.contains(url);
|
||||
_scriptUsers.insert(url, scriptUser);
|
||||
|
||||
if (alreadyWaiting) {
|
||||
qDebug() << "Already downloading script at:" << url.toString();
|
||||
} else {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest = QNetworkRequest(url);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
|
||||
qDebug() << "Downloading script at:" << url.toString();
|
||||
QNetworkReply* reply = networkAccessManager.get(networkRequest);
|
||||
connect(reply, &QNetworkReply::finished, this, &ScriptCache::scriptDownloaded);
|
||||
}
|
||||
}
|
||||
return scriptContents;
|
||||
}
|
||||
|
||||
void ScriptCache::scriptDownloaded() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
QUrl url = reply->url();
|
||||
QList<ScriptUser*> scriptUsers = _scriptUsers.values(url);
|
||||
_scriptUsers.remove(url);
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
|
||||
_scriptCache[url] = reply->readAll();
|
||||
qDebug() << "Done downloading script at:" << url.toString();
|
||||
|
||||
foreach(ScriptUser* user, scriptUsers) {
|
||||
user->scriptContentsAvailable(url, _scriptCache[url]);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << reply->url().toString();
|
||||
foreach(ScriptUser* user, scriptUsers) {
|
||||
user->errorInLoadingScript(url);
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
|
44
libraries/script-engine/src/ScriptCache.h
Normal file
44
libraries/script-engine/src/ScriptCache.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// ScriptCache.h
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015-03-30
|
||||
// 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_ScriptCache_h
|
||||
#define hifi_ScriptCache_h
|
||||
|
||||
#include <ResourceCache.h>
|
||||
|
||||
class ScriptUser {
|
||||
public:
|
||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) = 0;
|
||||
virtual void errorInLoadingScript(const QUrl& url) = 0;
|
||||
};
|
||||
|
||||
/// Interface for loading scripts
|
||||
class ScriptCache : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
QString getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending);
|
||||
void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); }
|
||||
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
|
||||
|
||||
private slots:
|
||||
void scriptDownloaded();
|
||||
|
||||
private:
|
||||
ScriptCache(QObject* parent = NULL);
|
||||
|
||||
QHash<QUrl, QString> _scriptCache;
|
||||
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
||||
QSet<QUrl> _badScripts;
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptCache_h
|
|
@ -34,6 +34,7 @@
|
|||
#include "EventTypes.h"
|
||||
#include "MenuItemProperties.h"
|
||||
#include "ScriptAudioInjector.h"
|
||||
#include "ScriptCache.h"
|
||||
#include "ScriptEngine.h"
|
||||
#include "TypedArrays.h"
|
||||
#include "XMLHttpRequestClass.h"
|
||||
|
@ -275,31 +276,26 @@ void ScriptEngine::loadURL(const QUrl& scriptURL) {
|
|||
_scriptContents = in.readAll();
|
||||
emit scriptLoaded(_fileNameString);
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << _fileNameString;
|
||||
qDebug() << "ERROR Loading file:" << _fileNameString << "line:" << __LINE__;
|
||||
emit errorLoadingScript(_fileNameString);
|
||||
}
|
||||
} else {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest = QNetworkRequest(url);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
QNetworkReply* reply = networkAccessManager.get(networkRequest);
|
||||
connect(reply, &QNetworkReply::finished, this, &ScriptEngine::handleScriptDownload);
|
||||
bool isPending;
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
scriptCache->getScript(url, this, isPending);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::handleScriptDownload() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
|
||||
_scriptContents = reply->readAll();
|
||||
emit scriptLoaded(_fileNameString);
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << reply->url().toString();
|
||||
emit errorLoadingScript(_fileNameString);
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
||||
_scriptContents = scriptContents;
|
||||
emit scriptLoaded(_fileNameString);
|
||||
}
|
||||
|
||||
void ScriptEngine::errorInLoadingScript(const QUrl& url) {
|
||||
qDebug() << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
|
||||
emit errorLoadingScript(_fileNameString); // ??
|
||||
}
|
||||
|
||||
void ScriptEngine::init() {
|
||||
|
@ -765,7 +761,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
for (QUrl url : urls) {
|
||||
QString contents = data[url];
|
||||
if (contents.isNull()) {
|
||||
qDebug() << "Error loading file: " << url;
|
||||
qDebug() << "Error loading file: " << url << "line:" << __LINE__;
|
||||
} else {
|
||||
QScriptValue result = evaluate(contents, url.toString());
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "ArrayBufferClass.h"
|
||||
#include "AudioScriptingInterface.h"
|
||||
#include "Quat.h"
|
||||
#include "ScriptCache.h"
|
||||
#include "ScriptUUID.h"
|
||||
#include "Vec3.h"
|
||||
|
||||
|
@ -35,7 +36,7 @@ const QString NO_SCRIPT("");
|
|||
|
||||
const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 1000) + 0.5);
|
||||
|
||||
class ScriptEngine : public QScriptEngine {
|
||||
class ScriptEngine : public QScriptEngine, public ScriptUser {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT,
|
||||
|
@ -94,6 +95,9 @@ public:
|
|||
|
||||
void waitTillDoneRunning();
|
||||
|
||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
||||
virtual void errorInLoadingScript(const QUrl& url);
|
||||
|
||||
public slots:
|
||||
void loadURL(const QUrl& scriptURL);
|
||||
void stop();
|
||||
|
@ -160,8 +164,6 @@ private:
|
|||
ArrayBufferClass* _arrayBufferClass;
|
||||
|
||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
private slots:
|
||||
void handleScriptDownload();
|
||||
|
||||
private:
|
||||
static QSet<ScriptEngine*> _allKnownScriptEngines;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#define hifi_Extents_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/extented_min_max.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
#include "StreamUtils.h"
|
||||
|
@ -46,6 +47,9 @@ public:
|
|||
/// rotate the extents around orign by rotation
|
||||
void rotate(const glm::quat& rotation);
|
||||
|
||||
glm::vec3 size() const { return maximum - minimum; }
|
||||
float largestDimension () const {glm::vec3 s = size(); return glm::max(s[0], s[1], s[2]); }
|
||||
|
||||
/// \return new Extents which is original rotated around orign by rotation
|
||||
Extents getRotated(const glm::quat& rotation) const {
|
||||
Extents temp = { minimum, maximum };
|
||||
|
@ -68,4 +72,4 @@ inline QDebug operator<<(QDebug debug, const Extents& extents) {
|
|||
}
|
||||
|
||||
|
||||
#endif // hifi_Extents_h
|
||||
#endif // hifi_Extents_h
|
||||
|
|
|
@ -23,6 +23,7 @@ void ShapeInfo::clear() {
|
|||
|
||||
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
|
||||
_type = type;
|
||||
_points.clear();
|
||||
switch(type) {
|
||||
case SHAPE_TYPE_NONE:
|
||||
_halfExtents = glm::vec3(0.0f);
|
||||
|
@ -37,6 +38,12 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
|
|||
break;
|
||||
}
|
||||
case SHAPE_TYPE_CONVEX_HULL:
|
||||
_url = QUrl(url);
|
||||
// halfExtents aren't used by convex-hull or compound convex-hull except as part of
|
||||
// the generation of the key for the ShapeManager.
|
||||
_halfExtents = halfExtents;
|
||||
break;
|
||||
case SHAPE_TYPE_COMPOUND:
|
||||
_url = QUrl(url);
|
||||
_halfExtents = halfExtents;
|
||||
break;
|
||||
|
@ -47,31 +54,44 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
|
|||
}
|
||||
|
||||
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_BOX;
|
||||
_halfExtents = halfExtents;
|
||||
_points.clear();
|
||||
_doubleHashKey.clear();
|
||||
}
|
||||
|
||||
void ShapeInfo::setSphere(float radius) {
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_SPHERE;
|
||||
_halfExtents = glm::vec3(radius, radius, radius);
|
||||
_points.clear();
|
||||
_doubleHashKey.clear();
|
||||
}
|
||||
|
||||
void ShapeInfo::setEllipsoid(const glm::vec3& halfExtents) {
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_ELLIPSOID;
|
||||
_halfExtents = halfExtents;
|
||||
_points.clear();
|
||||
_doubleHashKey.clear();
|
||||
}
|
||||
|
||||
void ShapeInfo::setConvexHull(const QVector<glm::vec3>& points) {
|
||||
_type = SHAPE_TYPE_CONVEX_HULL;
|
||||
void ShapeInfo::setConvexHulls(const QVector<QVector<glm::vec3>>& points) {
|
||||
if (points.size() == 1) {
|
||||
_type = SHAPE_TYPE_CONVEX_HULL;
|
||||
} else {
|
||||
_type = SHAPE_TYPE_COMPOUND;
|
||||
}
|
||||
_points = points;
|
||||
_doubleHashKey.clear();
|
||||
}
|
||||
|
||||
void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_CAPSULE_Y;
|
||||
_halfExtents = glm::vec3(radius, halfHeight, radius);
|
||||
_points.clear();
|
||||
_doubleHashKey.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -44,14 +44,14 @@ public:
|
|||
void setBox(const glm::vec3& halfExtents);
|
||||
void setSphere(float radius);
|
||||
void setEllipsoid(const glm::vec3& halfExtents);
|
||||
void setConvexHull(const QVector<glm::vec3>& points);
|
||||
void setConvexHulls(const QVector<QVector<glm::vec3>>& points);
|
||||
void setCapsuleY(float radius, float halfHeight);
|
||||
|
||||
const int getType() const { return _type; }
|
||||
|
||||
const glm::vec3& getHalfExtents() const { return _halfExtents; }
|
||||
|
||||
const QVector<glm::vec3>& getPoints() const { return _points; }
|
||||
const QVector<QVector<glm::vec3>>& getPoints() const { return _points; }
|
||||
|
||||
void clearPoints () { _points.clear(); }
|
||||
void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; }
|
||||
|
@ -64,8 +64,8 @@ protected:
|
|||
ShapeType _type = SHAPE_TYPE_NONE;
|
||||
glm::vec3 _halfExtents = glm::vec3(0.0f);
|
||||
DoubleHashKey _doubleHashKey;
|
||||
QVector<glm::vec3> _points; // points for convex collision hull
|
||||
QUrl _url; // url for model of convex collision hull
|
||||
QVector<QVector<glm::vec3>> _points; // points for convex collision hulls
|
||||
QUrl _url; // url for model of convex collision hulls
|
||||
};
|
||||
|
||||
#endif // hifi_ShapeInfo_h
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
SimpleMovingAverage::SimpleMovingAverage(int numSamplesToAverage) :
|
||||
_numSamples(0),
|
||||
_lastEventTimestamp(0),
|
||||
_average(0.0f),
|
||||
_eventDeltaAverage(0.0f),
|
||||
WEIGHTING(1.0f / numSamplesToAverage),
|
||||
|
|
|
@ -143,4 +143,12 @@ QDebug& operator<<(QDebug& dbg, const glm::mat4& m) {
|
|||
return dbg << " ]}";
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& dbg, const QVariantHash& v) {
|
||||
dbg.nospace() << "[";
|
||||
for (QVariantHash::const_iterator it = v.constBegin(); it != v.constEnd(); it++) {
|
||||
dbg << it.key() << ":" << it.value();
|
||||
}
|
||||
return dbg << " ]";
|
||||
}
|
||||
|
||||
#endif // QT_NO_DEBUG_STREAM
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QVariantHash>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
@ -54,6 +55,7 @@ QDebug& operator<<(QDebug& s, const glm::vec3& v);
|
|||
QDebug& operator<<(QDebug& s, const glm::vec4& v);
|
||||
QDebug& operator<<(QDebug& s, const glm::quat& q);
|
||||
QDebug& operator<<(QDebug& s, const glm::mat4& m);
|
||||
QDebug& operator<<(QDebug& dbg, const QVariantHash& v);
|
||||
#endif // QT_NO_DEBUG_STREAM
|
||||
|
||||
#endif // hifi_StreamUtils_h
|
||||
|
|
|
@ -8,5 +8,5 @@ set_target_properties(scribe PROPERTIES FOLDER "Tools")
|
|||
find_package(VHACD)
|
||||
if(VHACD_FOUND)
|
||||
add_subdirectory(vhacd)
|
||||
set_target_properties(vhacd PROPERTIES FOLDER "Tools")
|
||||
# set_target_properties(vhacd PROPERTIES FOLDER "Tools")
|
||||
endif()
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
rm -f TAGS
|
||||
|
||||
find . -name *.h -print | while read I
|
||||
find . -name *.h -print | grep -v build-ext |while read I
|
||||
do
|
||||
etags --append "$I"
|
||||
done
|
||||
|
||||
|
||||
find . -name *.cpp -print | grep -v 'moc_' | while read I
|
||||
find . -name *.cpp -print | grep -v 'moc_' | grep -v build-ext | while read I
|
||||
do
|
||||
etags --append "$I"
|
||||
done
|
||||
|
|
|
@ -44,7 +44,13 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re
|
|||
int count = 0;
|
||||
foreach(FBXMesh mesh, geometry.meshes) {
|
||||
//get vertices for each mesh
|
||||
QVector<glm::vec3> vertices = mesh.vertices;
|
||||
// QVector<glm::vec3> vertices = mesh.vertices;
|
||||
|
||||
|
||||
QVector<glm::vec3> vertices;
|
||||
foreach (glm::vec3 vertex, mesh.vertices) {
|
||||
vertices.append(glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f)));
|
||||
}
|
||||
|
||||
//get the triangle indices for each mesh
|
||||
QVector<int> triangles;
|
||||
|
@ -57,8 +63,15 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re
|
|||
if (triangles.count() <= 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
AABox aaBox;
|
||||
foreach (glm::vec3 p, vertices) {
|
||||
aaBox += p;
|
||||
}
|
||||
|
||||
results->perMeshVertices.append(vertices);
|
||||
results->perMeshTriangleIndices.append(triangles);
|
||||
results->perMeshLargestDimension.append(aaBox.getLargestDimension());
|
||||
count++;
|
||||
}
|
||||
|
||||
|
@ -66,23 +79,117 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re
|
|||
return true;
|
||||
}
|
||||
|
||||
bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params, vhacd::ComputeResults *results)const{
|
||||
|
||||
void vhacd::VHACDUtil::combineMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const {
|
||||
float largestDimension = 0;
|
||||
int indexStart = 0;
|
||||
|
||||
QVector<glm::vec3> emptyVertices;
|
||||
QVector<int> emptyTriangles;
|
||||
results->perMeshVertices.append(emptyVertices);
|
||||
results->perMeshTriangleIndices.append(emptyTriangles);
|
||||
results->perMeshLargestDimension.append(largestDimension);
|
||||
|
||||
for (int i = 0; i < meshes->meshCount; i++) {
|
||||
QVector<glm::vec3> vertices = meshes->perMeshVertices.at(i);
|
||||
QVector<int> triangles = meshes->perMeshTriangleIndices.at(i);
|
||||
const float largestDimension = meshes->perMeshLargestDimension.at(i);
|
||||
|
||||
for (int j = 0; j < triangles.size(); j++) {
|
||||
triangles[ j ] += indexStart;
|
||||
}
|
||||
indexStart += vertices.size();
|
||||
|
||||
results->perMeshVertices[0] << vertices;
|
||||
results->perMeshTriangleIndices[0] << triangles;
|
||||
if (results->perMeshLargestDimension[0] < largestDimension) {
|
||||
results->perMeshLargestDimension[0] = largestDimension;
|
||||
}
|
||||
}
|
||||
|
||||
results->meshCount = 1;
|
||||
}
|
||||
|
||||
|
||||
void vhacd::VHACDUtil::fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const {
|
||||
|
||||
for (int i = 0; i < meshes->meshCount; i++) {
|
||||
QVector<glm::vec3> vertices = meshes->perMeshVertices.at(i);
|
||||
QVector<int> triangles = meshes->perMeshTriangleIndices.at(i);
|
||||
const float largestDimension = meshes->perMeshLargestDimension.at(i);
|
||||
|
||||
results->perMeshVertices.append(vertices);
|
||||
results->perMeshTriangleIndices.append(triangles);
|
||||
results->perMeshLargestDimension.append(largestDimension);
|
||||
|
||||
for (int j = 0; j < triangles.size(); j += 3) {
|
||||
auto p0 = vertices[triangles[j]];
|
||||
auto p1 = vertices[triangles[j+1]];
|
||||
auto p2 = vertices[triangles[j+2]];
|
||||
|
||||
auto d0 = p1 - p0;
|
||||
auto d1 = p2 - p0;
|
||||
|
||||
auto cp = glm::cross(d0, d1);
|
||||
cp = 5.0f * glm::normalize(cp);
|
||||
|
||||
auto p3 = p0 + cp;
|
||||
auto p4 = p1 + cp;
|
||||
auto p5 = p2 + cp;
|
||||
|
||||
auto n = results->perMeshVertices.size();
|
||||
results->perMeshVertices[i] << p3 << p4 << p5;
|
||||
results->perMeshTriangleIndices[i] << n << n+1 << n+2;
|
||||
}
|
||||
|
||||
results->meshCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params,
|
||||
vhacd::ComputeResults *results,
|
||||
int startMeshIndex, int endMeshIndex, float minimumMeshSize) const {
|
||||
|
||||
// vhacd::LoadFBXResults *meshes = new vhacd::LoadFBXResults;
|
||||
// combineMeshes(inMeshes, meshes);
|
||||
|
||||
// vhacd::LoadFBXResults *meshes = new vhacd::LoadFBXResults;
|
||||
// fattenMeshes(inMeshes, meshes);
|
||||
|
||||
|
||||
VHACD::IVHACD * interfaceVHACD = VHACD::CreateVHACD();
|
||||
int meshCount = meshes->meshCount;
|
||||
int count = 0;
|
||||
std::cout << "Performing V-HACD computation on " << meshCount << " meshes ..... " << std::endl;
|
||||
|
||||
for (int i = 0; i < meshCount; i++){
|
||||
if (startMeshIndex < 0) {
|
||||
startMeshIndex = 0;
|
||||
}
|
||||
if (endMeshIndex < 0) {
|
||||
endMeshIndex = meshCount;
|
||||
}
|
||||
|
||||
for (int i = startMeshIndex; i < endMeshIndex; i++){
|
||||
qDebug() << "--------------------";
|
||||
std::vector<glm::vec3> vertices = meshes->perMeshVertices.at(i).toStdVector();
|
||||
std::vector<int> triangles = meshes->perMeshTriangleIndices.at(i).toStdVector();
|
||||
int nPoints = (unsigned int)vertices.size();
|
||||
int nTriangles = (unsigned int)triangles.size() / 3;
|
||||
std::cout << "Mesh " << i + 1 << " : ";
|
||||
const float largestDimension = meshes->perMeshLargestDimension.at(i);
|
||||
|
||||
qDebug() << "Mesh " << i << " -- " << nPoints << " points, " << nTriangles << " triangles, "
|
||||
<< "size =" << largestDimension;
|
||||
|
||||
if (largestDimension < minimumMeshSize /* || largestDimension > 1000 */) {
|
||||
qDebug() << " Skipping...";
|
||||
continue;
|
||||
}
|
||||
// compute approximate convex decomposition
|
||||
bool res = interfaceVHACD->Compute(&vertices[0].x, 3, nPoints, &triangles[0], 3, nTriangles, params);
|
||||
if (!res){
|
||||
std::cout << "V-HACD computation failed for Mesh : " << i + 1 << std::endl;
|
||||
qDebug() << "V-HACD computation failed for Mesh : " << i;
|
||||
continue;
|
||||
}
|
||||
count++; //For counting number of successfull computations
|
||||
|
@ -111,8 +218,6 @@ bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD
|
|||
m_triangles_copy[ i ] = hull.m_triangles[ i ];
|
||||
}
|
||||
hull.m_triangles = m_triangles_copy;
|
||||
|
||||
|
||||
convexHulls.append(hull);
|
||||
}
|
||||
results->convexHullList.append(convexHulls);
|
||||
|
|
|
@ -34,12 +34,16 @@ namespace vhacd {
|
|||
int meshCount;
|
||||
QVector<QVector<glm::vec3>> perMeshVertices;
|
||||
QVector<QVector<int>> perMeshTriangleIndices;
|
||||
QVector<float> perMeshLargestDimension;
|
||||
} LoadFBXResults;
|
||||
|
||||
class VHACDUtil {
|
||||
public:
|
||||
bool loadFBX(const QString filename, vhacd::LoadFBXResults *results);
|
||||
bool computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params, vhacd::ComputeResults *results)const;
|
||||
void combineMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const;
|
||||
void fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const;
|
||||
bool computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params,
|
||||
vhacd::ComputeResults *results, int startMeshIndex, int endMeshIndex, float minimumMeshSize) const;
|
||||
~VHACDUtil();
|
||||
};
|
||||
|
||||
|
|
|
@ -98,6 +98,15 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
const QCommandLineOption outputFilenameOption("o", "output file", "filename.obj");
|
||||
parser.addOption(outputFilenameOption);
|
||||
|
||||
const QCommandLineOption startMeshIndexOption("s", "start-mesh index", "0");
|
||||
parser.addOption(startMeshIndexOption);
|
||||
|
||||
const QCommandLineOption endMeshIndexOption("e", "end-mesh index", "0");
|
||||
parser.addOption(endMeshIndexOption);
|
||||
|
||||
const QCommandLineOption minimumMeshSizeOption("m", "minimum mesh size to consider", "0");
|
||||
parser.addOption(minimumMeshSizeOption);
|
||||
|
||||
|
||||
if (!parser.parse(QCoreApplication::arguments())) {
|
||||
qCritical() << parser.errorText() << endl;
|
||||
|
@ -138,13 +147,31 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
int startMeshIndex = -1;
|
||||
// check for an assignment pool passed on the command line or in the config
|
||||
if (parser.isSet(startMeshIndexOption)) {
|
||||
startMeshIndex = parser.value(startMeshIndexOption).toInt();
|
||||
}
|
||||
|
||||
int endMeshIndex = -1;
|
||||
// check for an assignment pool passed on the command line or in the config
|
||||
if (parser.isSet(endMeshIndexOption)) {
|
||||
endMeshIndex = parser.value(endMeshIndexOption).toInt();
|
||||
}
|
||||
|
||||
float minimumMeshSize = 0.0f;
|
||||
// check for an assignment pool passed on the command line or in the config
|
||||
if (parser.isSet(minimumMeshSizeOption)) {
|
||||
minimumMeshSize = parser.value(minimumMeshSizeOption).toFloat();
|
||||
}
|
||||
|
||||
|
||||
//set parameters for V-HACD
|
||||
params.m_callback = &pCallBack; //progress callback
|
||||
params.m_resolution = 100000; // 100000
|
||||
params.m_depth = 20; // 20
|
||||
params.m_concavity = 0.001; // 0.001
|
||||
params.m_delta = 0.01; // 0.05
|
||||
params.m_delta = 0.05; // 0.05
|
||||
params.m_planeDownsampling = 4; // 4
|
||||
params.m_convexhullDownsampling = 4; // 4
|
||||
params.m_alpha = 0.05; // 0.05 // controls the bias toward clipping along symmetry planes
|
||||
|
@ -153,7 +180,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
params.m_pca = 0; // 0 enable/disable normalizing the mesh before applying the convex decomposition
|
||||
params.m_mode = 0; // 0: voxel-based (recommended), 1: tetrahedron-based
|
||||
params.m_maxNumVerticesPerCH = 64; // 64
|
||||
params.m_minVolumePerCH = 0.00001; // 0.0001
|
||||
params.m_minVolumePerCH = 0.0001; // 0.0001
|
||||
params.m_callback = 0; // 0
|
||||
params.m_logger = 0; // 0
|
||||
params.m_convexhullApproximation = true; // true
|
||||
|
@ -172,7 +199,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
|
|||
begin = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
if (!vUtil.computeVHACD(&fbx, params, &results)){
|
||||
if (!vUtil.computeVHACD(&fbx, params, &results, startMeshIndex, endMeshIndex, minimumMeshSize)) {
|
||||
cout << "Compute Failed...";
|
||||
}
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
|
|
Loading…
Reference in a new issue