This commit is contained in:
Stephen Birarda 2015-03-31 12:34:47 -07:00
commit d55ae62459
35 changed files with 744 additions and 229 deletions

View file

@ -45,6 +45,9 @@ var diceButton = Overlays.addOverlay("image", {
var GRAVITY = -3.5; var GRAVITY = -3.5;
var LIFETIME = 300; var LIFETIME = 300;
// NOTE: angularVelocity is in radians/sec
var MAX_ANGULAR_SPEED = Math.PI;
function shootDice(position, velocity) { function shootDice(position, velocity) {
for (var i = 0; i < NUMBER_OF_DICE; i++) { for (var i = 0; i < NUMBER_OF_DICE; i++) {
dice.push(Entities.addEntity( dice.push(Entities.addEntity(
@ -53,9 +56,7 @@ function shootDice(position, velocity) {
position: position, position: position,
velocity: velocity, velocity: velocity,
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360), rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
// NOTE: angularVelocity is in radians/sec angularVelocity: { x: Math.random() * MAX_ANGULAR_SPEED, y: Math.random() * MAX_ANGULAR_SPEED, z: Math.random() * MAX_ANGULAR_SPEED },
var maxAngularSpeed = Math.PI;
angularVelocity: { x: Math.random() * maxAngularSpeed, y: Math.random() * maxAngularSpeed, z: Math.random() * maxAngularSpeed },
lifetime: LIFETIME, lifetime: LIFETIME,
gravity: { x: 0, y: GRAVITY, z: 0 }, gravity: { x: 0, y: GRAVITY, z: 0 },
shapeType: "box", shapeType: "box",

View file

@ -70,6 +70,7 @@
var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0); var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0);
var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation())); var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation()));
var housePositions = []
for (var j = 0; j < measures.rows; j++) { for (var j = 0; j < measures.rows; j++) {
var posX1 = 0 - (xRange / 2); var posX1 = 0 - (xRange / 2);
@ -87,11 +88,8 @@
y: 0, y: 0,
z: dd z: dd
}; };
print("House nr.:" + (houses.length + 1)); housePositions.push(Vec3.sum(housePos, posShift));
houses.push(
addHouseAt(Vec3.sum(housePos, posShift), (j % 2 == 0) ? rotEven : rotOdd)
);
posX1 += measures.parcelWidth; posX1 += measures.parcelWidth;
} }
} }
@ -144,14 +142,21 @@
}; };
} }
function cleanup() { var addHouses = function() {
while (houses.length > 0) { if (housePositions.length > 0) {
if (!houses[0].isKnownID) { position = housePositions.pop();
houses[0] = Entities.identifyEntity(houses[0]); print("House nr.:" + (houses.length + 1));
} houses.push(
Entities.deleteEntity(houses.shift()); addHouseAt(position, (housePositions.length % 2 == 0) ? rotEven : rotOdd)
);
// max 20 per second
Script.setTimeout(addHouses, 50);
} else {
Script.stop();
} }
} };
Script.scriptEnding.connect(cleanup); addHouses();
})(); })();

View file

@ -76,7 +76,7 @@
#include <PhysicsEngine.h> #include <PhysicsEngine.h>
#include <ProgramObject.h> #include <ProgramObject.h>
#include <ResourceCache.h> #include <ResourceCache.h>
//#include <ScriptCache.h> #include <ScriptCache.h>
#include <SettingHandle.h> #include <SettingHandle.h>
#include <SoundCache.h> #include <SoundCache.h>
#include <TextRenderer.h> #include <TextRenderer.h>
@ -221,7 +221,7 @@ bool setupEssentials(int& argc, char** argv) {
auto addressManager = DependencyManager::set<AddressManager>(); auto addressManager = DependencyManager::set<AddressManager>();
auto nodeList = DependencyManager::set<NodeList>(NodeType::Agent, listenPort); auto nodeList = DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
auto geometryCache = DependencyManager::set<GeometryCache>(); auto geometryCache = DependencyManager::set<GeometryCache>();
//auto scriptCache = DependencyManager::set<ScriptCache>(); auto scriptCache = DependencyManager::set<ScriptCache>();
auto soundCache = DependencyManager::set<SoundCache>(); auto soundCache = DependencyManager::set<SoundCache>();
auto glowEffect = DependencyManager::set<GlowEffect>(); auto glowEffect = DependencyManager::set<GlowEffect>();
auto faceshift = DependencyManager::set<Faceshift>(); auto faceshift = DependencyManager::set<Faceshift>();
@ -620,7 +620,7 @@ Application::~Application() {
DependencyManager::destroy<AnimationCache>(); DependencyManager::destroy<AnimationCache>();
DependencyManager::destroy<TextureCache>(); DependencyManager::destroy<TextureCache>();
DependencyManager::destroy<GeometryCache>(); DependencyManager::destroy<GeometryCache>();
//DependencyManager::destroy<ScriptCache>(); DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<SoundCache>(); DependencyManager::destroy<SoundCache>();
QThread* nodeThread = DependencyManager::get<NodeList>()->thread(); QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
@ -1524,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 // 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. // details normally.
bool showWarnings = getLogger()->extraDebugging(); bool showWarnings = getLogger()->extraDebugging();
PerformanceWarning warn(showWarnings, "idle()"); PerformanceWarning warn(showWarnings, "idle()");
@ -2540,7 +2540,7 @@ bool Application::isHMDMode() const {
QRect Application::getDesirableApplicationGeometry() { QRect Application::getDesirableApplicationGeometry() {
QRect applicationGeometry = getWindow()->geometry(); 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. // the "main screen" geometry.
HMDToolsDialog* hmdTools = DependencyManager::get<DialogsManager>()->getHMDToolsDialog(); HMDToolsDialog* hmdTools = DependencyManager::get<DialogsManager>()->getHMDToolsDialog();
if (hmdTools && hmdTools->hasHMDScreen()) { if (hmdTools && hmdTools->hasHMDScreen()) {
@ -3376,7 +3376,7 @@ void Application::nodeKilled(SharedNodePointer node) {
void Application::trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket) { 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) { if (sendingNode) {
QUuid nodeUUID = sendingNode->getUUID(); QUuid nodeUUID = sendingNode->getUUID();
@ -3445,7 +3445,7 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin
} }
// store jurisdiction details for later use // 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 // 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 // details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap; JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes()); jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes());
@ -3665,17 +3665,56 @@ bool Application::askToSetAvatarUrl(const QString& url) {
msgBox.exec(); msgBox.exec();
return false; 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; QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Question); msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle("Set Avatar"); 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); case FSTReader::BODY_ONLY_MODEL:
QPushButton* bodyButton = msgBox.addButton(tr("Body"), QMessageBox::ActionRole); message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar body?");
QPushButton* bodyAndHeadButton = msgBox.addButton(tr("Body + Head"), QMessageBox::ActionRole); 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.addButton(QMessageBox::Cancel);
msgBox.exec(); msgBox.exec();
@ -3688,6 +3727,11 @@ bool Application::askToSetAvatarUrl(const QString& url) {
} else if (msgBox.clickedButton() == bodyButton) { } else if (msgBox.clickedButton() == bodyButton) {
qDebug() << "Chose to use for body: " << url; qDebug() << "Chose to use for body: " << url;
_myAvatar->setSkeletonModelURL(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); UserActivityLogger::getInstance().changedModel("skeleton", url);
_myAvatar->sendIdentityPacket(); _myAvatar->sendIdentityPacket();
} else if (msgBox.clickedButton() == bodyAndHeadButton) { } else if (msgBox.clickedButton() == bodyAndHeadButton) {

View file

@ -246,7 +246,7 @@ namespace MenuOption {
const QString ToolWindow = "Tool Window"; const QString ToolWindow = "Tool Window";
const QString TransmitterDrive = "Transmitter Drive"; const QString TransmitterDrive = "Transmitter Drive";
const QString TurnWithHead = "Turn using Head"; const QString TurnWithHead = "Turn using Head";
const QString PackageModel = "Package Model"; const QString PackageModel = "Package Model...";
const QString Visage = "Visage"; const QString Visage = "Visage";
const QString Wireframe = "Wireframe"; const QString Wireframe = "Wireframe";
} }

View file

@ -85,7 +85,7 @@ bool ModelPackager::loadModel() {
return false; return false;
} }
qDebug() << "Reading FST file : " << _modelFile.filePath(); qDebug() << "Reading FST file : " << _modelFile.filePath();
_mapping = readMapping(fst.readAll()); _mapping = FSTReader::readMapping(fst.readAll());
fst.close(); fst.close();
_fbxInfo = QFileInfo(_modelFile.path() + "/" + _mapping.value(FILENAME_FIELD).toString()); _fbxInfo = QFileInfo(_modelFile.path() + "/" + _mapping.value(FILENAME_FIELD).toString());
@ -119,21 +119,23 @@ bool ModelPackager::editProperties() {
return false; return false;
} }
_mapping = properties.getMapping(); _mapping = properties.getMapping();
// Make sure that a mapping for the root joint has been specified if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash(); // Make sure that a mapping for the root joint has been specified
if (!joints.contains("jointRoot")) { QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName()); 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."; QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled.";
QMessageBox msgBox; QMessageBox msgBox;
msgBox.setWindowTitle("Model Upload"); msgBox.setWindowTitle("Model Packager");
msgBox.setText(message); msgBox.setText(message);
msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Warning); msgBox.setIcon(QMessageBox::Warning);
msgBox.exec(); msgBox.exec();
return false; return false;
}
} }
return true; return true;
@ -183,7 +185,7 @@ bool ModelPackager::zipModel() {
// Copy FST // Copy FST
QFile fst(tempDir.path() + "/" + nameField + ".fst"); QFile fst(tempDir.path() + "/" + nameField + ".fst");
if (fst.open(QIODevice::WriteOnly)) { if (fst.open(QIODevice::WriteOnly)) {
fst.write(writeMapping(_mapping)); fst.write(FSTReader::writeMapping(_mapping));
fst.close(); fst.close();
} else { } else {
qDebug() << "Couldn't write FST file" << fst.fileName(); 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) { 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)) { if (!mapping.contains(NAME_FIELD)) {
mapping.insert(NAME_FIELD, QFileInfo(filename).baseName()); mapping.insert(NAME_FIELD, QFileInfo(filename).baseName());
} }
@ -232,39 +246,40 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
if (!joints.contains("jointNeck")) { if (!joints.contains("jointNeck")) {
joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck"); joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck");
} }
if (!joints.contains("jointRoot")) {
joints.insert("jointRoot", "Hips"); if (isBodyType) {
} if (!joints.contains("jointRoot")) {
if (!joints.contains("jointLean")) { joints.insert("jointRoot", "Hips");
joints.insert("jointLean", "Spine"); }
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")) { 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"); 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); mapping.insert(JOINT_FIELD, joints);
if (!mapping.contains(FREE_JOINT_FIELD)) {
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); if (isBodyType) {
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); if (!mapping.contains(FREE_JOINT_FIELD)) {
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); 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 // If there are no blendshape mappings, and we detect that this is likely a mixamo file,
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file // then we can add the default mixamo to "faceshift" mappings
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(BLENDSHAPE_FIELD) && likelyMixamoFile) { if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) {
QVariantHash blendshapes; QVariantHash blendshapes;
blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);

View file

@ -35,7 +35,7 @@ private:
QFileInfo _modelFile; QFileInfo _modelFile;
QFileInfo _fbxInfo; QFileInfo _fbxInfo;
ModelType _modelType; FSTReader::ModelType _modelType;
QString _texDir; QString _texDir;
QVariantHash _mapping; QVariantHash _mapping;

View file

@ -25,7 +25,7 @@
#include "ModelPropertiesDialog.h" #include "ModelPropertiesDialog.h"
ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
const QString& basePath, const FBXGeometry& geometry) : const QString& basePath, const FBXGeometry& geometry) :
_modelType(modelType), _modelType(modelType),
_originalMapping(originalMapping), _originalMapping(originalMapping),
@ -46,8 +46,8 @@ _geometry(geometry)
_scale->setMaximum(FLT_MAX); _scale->setMaximum(FLT_MAX);
_scale->setSingleStep(0.01); _scale->setSingleStep(0.01);
if (_modelType != ENTITY_MODEL) { if (_modelType != FSTReader::ENTITY_MODEL) {
if (_modelType == ATTACHMENT_MODEL) { if (_modelType == FSTReader::ATTACHMENT_MODEL) {
QHBoxLayout* translation = new QHBoxLayout(); QHBoxLayout* translation = new QHBoxLayout();
form->addRow("Translation:", translation); form->addRow("Translation:", translation);
translation->addWidget(_translationX = createTranslationBox()); translation->addWidget(_translationX = createTranslationBox());
@ -63,7 +63,7 @@ _geometry(geometry)
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox()); form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
form->addRow("Neck Joint:", _neckJoint = 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("Root Joint:", _rootJoint = createJointBox());
form->addRow("Lean Joint:", _leanJoint = createJointBox()); form->addRow("Lean Joint:", _leanJoint = createJointBox());
form->addRow("Head Joint:", _headJoint = createJointBox()); form->addRow("Head Joint:", _headJoint = createJointBox());
@ -89,8 +89,14 @@ _geometry(geometry)
reset(); reset();
} }
QString ModelPropertiesDialog::getType() const {
return FSTReader::getNameFromType(_modelType);
}
QVariantHash ModelPropertiesDialog::getMapping() const { QVariantHash ModelPropertiesDialog::getMapping() const {
QVariantHash mapping = _originalMapping; QVariantHash mapping = _originalMapping;
mapping.insert(TYPE_FIELD, getType());
mapping.insert(NAME_FIELD, _name->text()); mapping.insert(NAME_FIELD, _name->text());
mapping.insert(TEXDIR_FIELD, _textureDirectory->text()); mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
mapping.insert(SCALE_FIELD, QString::number(_scale->value())); mapping.insert(SCALE_FIELD, QString::number(_scale->value()));
@ -102,9 +108,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
} }
mapping.insert(JOINT_INDEX_FIELD, jointIndices); mapping.insert(JOINT_INDEX_FIELD, jointIndices);
if (_modelType != ENTITY_MODEL) { if (_modelType != FSTReader::ENTITY_MODEL) {
QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
if (_modelType == ATTACHMENT_MODEL) { if (_modelType == FSTReader::ATTACHMENT_MODEL) {
glm::vec3 pivot; glm::vec3 pivot;
if (_pivotAboutCenter->isChecked()) { if (_pivotAboutCenter->isChecked()) {
pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f; pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f;
@ -121,7 +127,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText()); insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
insertJointMapping(joints, "jointNeck", _neckJoint->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, "jointRoot", _rootJoint->currentText());
insertJointMapping(joints, "jointLean", _leanJoint->currentText()); insertJointMapping(joints, "jointLean", _leanJoint->currentText());
insertJointMapping(joints, "jointHead", _headJoint->currentText()); insertJointMapping(joints, "jointHead", _headJoint->currentText());
@ -151,8 +159,8 @@ void ModelPropertiesDialog::reset() {
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
if (_modelType != ENTITY_MODEL) { if (_modelType != FSTReader::ENTITY_MODEL) {
if (_modelType == ATTACHMENT_MODEL) { if (_modelType == FSTReader::ATTACHMENT_MODEL) {
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble()); _translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble()); _translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble()); _translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
@ -164,7 +172,8 @@ void ModelPropertiesDialog::reset() {
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString()); setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
setJointText(_neckJoint, jointHash.value("jointNeck").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(_rootJoint, jointHash.value("jointRoot").toString());
setJointText(_leanJoint, jointHash.value("jointLean").toString()); setJointText(_leanJoint, jointHash.value("jointLean").toString());
setJointText(_headJoint, jointHash.value("jointHead").toString()); setJointText(_headJoint, jointHash.value("jointHead").toString());

View file

@ -15,6 +15,7 @@
#include <QDialog> #include <QDialog>
#include <FBXReader.h> #include <FBXReader.h>
#include <FSTReader.h>
#include "ui/ModelsBrowser.h" #include "ui/ModelsBrowser.h"
@ -28,7 +29,7 @@ class ModelPropertiesDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
const QString& basePath, const FBXGeometry& geometry); const QString& basePath, const FBXGeometry& geometry);
QVariantHash getMapping() const; QVariantHash getMapping() const;
@ -43,8 +44,9 @@ private:
QComboBox* createJointBox(bool withNone = true) const; QComboBox* createJointBox(bool withNone = true) const;
QDoubleSpinBox* createTranslationBox() const; QDoubleSpinBox* createTranslationBox() const;
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const; void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
QString getType() const;
ModelType _modelType; FSTReader::ModelType _modelType;
QVariantHash _originalMapping; QVariantHash _originalMapping;
QString _basePath; QString _basePath;
FBXGeometry _geometry; FBXGeometry _geometry;

View file

@ -18,8 +18,9 @@
#include "ModelSelector.h" #include "ModelSelector.h"
static const QString AVATAR_HEAD_STRING = "Avatar Head"; static const QString AVATAR_HEAD_STRING = "Avatar Head Only";
static const QString AVATAR_BODY_STRING = "Avatar Body"; 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 AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
static const QString ENTITY_MODEL_STRING = "Entity Model"; static const QString ENTITY_MODEL_STRING = "Entity Model";
@ -36,6 +37,7 @@ ModelSelector::ModelSelector() {
_modelType = new QComboBox(this); _modelType = new QComboBox(this);
_modelType->addItem(AVATAR_HEAD_STRING); _modelType->addItem(AVATAR_HEAD_STRING);
_modelType->addItem(AVATAR_BODY_STRING); _modelType->addItem(AVATAR_BODY_STRING);
_modelType->addItem(AVATAR_HEAD_AND_BODY_STRING);
_modelType->addItem(AVATAR_ATTACHEMENT_STRING); _modelType->addItem(AVATAR_ATTACHEMENT_STRING);
_modelType->addItem(ENTITY_MODEL_STRING); _modelType->addItem(ENTITY_MODEL_STRING);
form->addRow("Model Type:", _modelType); form->addRow("Model Type:", _modelType);
@ -50,17 +52,19 @@ QFileInfo ModelSelector::getFileInfo() const {
return _modelFile; return _modelFile;
} }
ModelType ModelSelector::getModelType() const { FSTReader::ModelType ModelSelector::getModelType() const {
QString text = _modelType->currentText(); QString text = _modelType->currentText();
if (text == AVATAR_HEAD_STRING) { if (text == AVATAR_HEAD_STRING) {
return HEAD_MODEL; return FSTReader::HEAD_MODEL;
} else if (text == AVATAR_BODY_STRING) { } 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) { } else if (text == AVATAR_ATTACHEMENT_STRING) {
return ATTACHMENT_MODEL; return FSTReader::ATTACHMENT_MODEL;
} else if (text == ENTITY_MODEL_STRING) { } else if (text == ENTITY_MODEL_STRING) {
return ENTITY_MODEL; return FSTReader::ENTITY_MODEL;
} else { } else {
Q_UNREACHABLE(); Q_UNREACHABLE();
} }

View file

@ -29,7 +29,7 @@ public:
ModelSelector(); ModelSelector();
QFileInfo getFileInfo() const; QFileInfo getFileInfo() const;
ModelType getModelType() const; FSTReader::ModelType getModelType() const;
public slots: public slots:
virtual void accept(); virtual void accept();

View file

@ -638,7 +638,7 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
/// \param const QString& nameFilter filter to filter filenames /// \param const QString& nameFilter filter to filter filenames
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::showS3Browse(const QString& nameFilter) { QScriptValue WindowScriptingInterface::showS3Browse(const QString& nameFilter) {
ModelsBrowser browser(ENTITY_MODEL); ModelsBrowser browser(FSTReader::ENTITY_MODEL);
if (nameFilter != "") { if (nameFilter != "") {
browser.setNameFilter(nameFilter); browser.setNameFilter(nameFilter);
} }

View file

@ -164,7 +164,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const {
} }
void AttachmentPanel::chooseModelURL() { void AttachmentPanel::chooseModelURL() {
ModelsBrowser modelBrowser(ATTACHMENT_MODEL, this); ModelsBrowser modelBrowser(FSTReader::ATTACHMENT_MODEL, this);
connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&))); connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&)));
modelBrowser.browse(); modelBrowser.browse();
} }

View file

@ -28,7 +28,7 @@
#include "ModelsBrowser.h" #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 S3_URL = "http://s3.amazonaws.com/hifi-public";
static const QString PUBLIC_URL = "http://public.highfidelity.io"; static const QString PUBLIC_URL = "http://public.highfidelity.io";
@ -71,7 +71,7 @@ static const QString propertiesIds[MODEL_METADATA_COUNT] = {
"Tags" "Tags"
}; };
ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) : ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) :
QWidget(parent, Qt::WindowStaysOnTopHint), QWidget(parent, Qt::WindowStaysOnTopHint),
_handler(new ModelHandler(modelsType)) _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), QObject(parent),
_initiateExit(false), _initiateExit(false),
_type(modelsType), _type(modelsType),

View file

@ -16,21 +16,16 @@
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QTreeView> #include <QTreeView>
class QNetworkReply; #include <FSTReader.h>
enum ModelType { class QNetworkReply;
ENTITY_MODEL,
HEAD_MODEL,
SKELETON_MODEL,
ATTACHMENT_MODEL
};
extern const char* MODEL_TYPE_NAMES[]; extern const char* MODEL_TYPE_NAMES[];
class ModelHandler : public QObject { class ModelHandler : public QObject {
Q_OBJECT Q_OBJECT
public: public:
ModelHandler(ModelType modelsType, QWidget* parent = NULL); ModelHandler(FSTReader::ModelType modelsType, QWidget* parent = NULL);
void lockModel() { _lock.lockForRead(); } void lockModel() { _lock.lockForRead(); }
QStandardItemModel* getModel() { return &_model; } QStandardItemModel* getModel() { return &_model; }
@ -51,7 +46,7 @@ private slots:
private: private:
bool _initiateExit; bool _initiateExit;
ModelType _type; FSTReader::ModelType _type;
QReadWriteLock _lock; QReadWriteLock _lock;
QStandardItemModel _model; QStandardItemModel _model;
QString _nameFilter; QString _nameFilter;
@ -66,7 +61,7 @@ class ModelsBrowser : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
ModelsBrowser(ModelType modelsType, QWidget* parent = NULL); ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent = NULL);
QString getSelectedFile() { return _selectedFile; } QString getSelectedFile() { return _selectedFile; }
signals: signals:

View file

@ -67,13 +67,13 @@ void PreferencesDialog::setSkeletonUrl(QString modelUrl) {
} }
void PreferencesDialog::openHeadModelBrowser() { void PreferencesDialog::openHeadModelBrowser() {
ModelsBrowser modelBrowser(HEAD_MODEL); ModelsBrowser modelBrowser(FSTReader::HEAD_MODEL);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl); connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl);
modelBrowser.browse(); modelBrowser.browse();
} }
void PreferencesDialog::openBodyModelBrowser() { void PreferencesDialog::openBodyModelBrowser() {
ModelsBrowser modelBrowser(SKELETON_MODEL); ModelsBrowser modelBrowser(FSTReader::HEAD_AND_BODY_MODEL);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl); connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl);
modelBrowser.browse(); modelBrowser.browse();
} }

View file

@ -110,14 +110,30 @@ void EntityTreeRenderer::shutdown() {
_shuttingDown = true; _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); 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); QUrl url(scriptMaybeURLorText);
// If the url is not valid, this must be script text... // If the url is not valid, this must be script text...
@ -126,6 +142,7 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
return scriptMaybeURLorText; return scriptMaybeURLorText;
} }
isURL = true; isURL = true;
urlOut = url;
QString scriptContents; // assume empty QString scriptContents; // assume empty
@ -148,20 +165,11 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
qDebug() << "ERROR Loading file:" << fileName; qDebug() << "ERROR Loading file:" << fileName;
} }
} else { } else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); auto scriptCache = DependencyManager::get<ScriptCache>();
QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); if (!scriptCache->isInBadScriptList(url)) {
QNetworkReply* reply = networkAccessManager.get(networkRequest); scriptContents = scriptCache->getScript(url, this, isPending);
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();
} }
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) { if (_shuttingDown) {
return QScriptValue(); // since we're shutting down, we don't load any more scripts 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. 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); QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(scriptContents);
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
@ -211,6 +236,9 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
qDebug() << " " << syntaxCheck.errorMessage() << ":" qDebug() << " " << syntaxCheck.errorMessage() << ":"
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber(); << syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
qDebug() << " SCRIPT:" << entityScript; qDebug() << " SCRIPT:" << entityScript;
scriptCache->addScriptToBadScriptList(url);
return QScriptValue(); // invalid script return QScriptValue(); // invalid script
} }
@ -223,6 +251,9 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
qDebug() << " NOT CONSTRUCTOR"; qDebug() << " NOT CONSTRUCTOR";
qDebug() << " SCRIPT:" << entityScript; qDebug() << " SCRIPT:" << entityScript;
scriptCache->addScriptToBadScriptList(url);
return QScriptValue(); // invalid script return QScriptValue(); // invalid script
} else { } else {
entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents); entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents);
@ -920,7 +951,7 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID) {
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID) { void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID) {
if (_tree && !_shuttingDown) { if (_tree && !_shuttingDown) {
// load the entity script if needed... // load the entity script if needed...
QScriptValue entityScript = loadEntityScript(entityID); QScriptValue entityScript = loadEntityScript(entityID, true); // is preload!
if (entityScript.property("preload").isValid()) { if (entityScript.property("preload").isValid()) {
QScriptValueList entityArgs = createEntityArgs(entityID); QScriptValueList entityArgs = createEntityArgs(entityID);
entityScript.property("preload").call(entityScript, entityArgs); entityScript.property("preload").call(entityScript, entityArgs);

View file

@ -16,6 +16,7 @@
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult #include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
#include <MouseEvent.h> #include <MouseEvent.h>
#include <OctreeRenderer.h> #include <OctreeRenderer.h>
#include <ScriptCache.h>
class Model; class Model;
class ScriptEngine; class ScriptEngine;
@ -31,7 +32,7 @@ public:
}; };
// Generic client side Octree renderer class. // Generic client side Octree renderer class.
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService { class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser {
Q_OBJECT Q_OBJECT
public: public:
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
@ -84,6 +85,9 @@ public:
/// hovering over, and entering entities /// hovering over, and entering entities
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface); void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
virtual void errorInLoadingScript(const QUrl& url);
signals: signals:
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
@ -138,10 +142,10 @@ private:
ScriptEngine* _entitiesScriptEngine; ScriptEngine* _entitiesScriptEngine;
ScriptEngine* _sandboxScriptEngine; ScriptEngine* _sandboxScriptEngine;
QScriptValue loadEntityScript(EntityItem* entity); QScriptValue loadEntityScript(EntityItem* entity, bool isPreload = false);
QScriptValue loadEntityScript(const EntityItemID& entityItemID); QScriptValue loadEntityScript(const EntityItemID& entityItemID, bool isPreload = false);
QScriptValue getPreviouslyLoadedEntityScript(const EntityItemID& entityItemID); 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, QMouseEvent* event, unsigned int deviceID);
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent); QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent);
@ -157,6 +161,8 @@ private:
bool _dontDoPrecisionPicking; bool _dontDoPrecisionPicking;
bool _shuttingDown = false; bool _shuttingDown = false;
QMultiMap<QUrl, EntityItemID> _waitingOnPreload;
}; };

View file

@ -317,29 +317,30 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
AABox aaBox; AABox aaBox;
_points.clear(); _points.clear();
unsigned int i = 0; unsigned int i = 0;
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
// 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) {
// each meshPart is a convex hull
foreach (const FBXMeshPart &meshPart, mesh.parts) { foreach (const FBXMeshPart &meshPart, mesh.parts) {
QVector<glm::vec3> pointsInPart; QVector<glm::vec3> pointsInPart;
// run through all the triangles and (uniquely) add each point to the hull
unsigned int triangleCount = meshPart.triangleIndices.size() / 3; unsigned int triangleCount = meshPart.triangleIndices.size() / 3;
assert((unsigned int)meshPart.triangleIndices.size() == triangleCount*3); assert((unsigned int)meshPart.triangleIndices.size() == triangleCount*3);
for (unsigned int j = 0; j < triangleCount; j++) { for (unsigned int j = 0; j < triangleCount; j++) {
unsigned int p0Index = meshPart.triangleIndices[j*3]; unsigned int p0Index = meshPart.triangleIndices[j*3];
unsigned int p1Index = meshPart.triangleIndices[j*3+1]; unsigned int p1Index = meshPart.triangleIndices[j*3+1];
unsigned int p2Index = meshPart.triangleIndices[j*3+2]; unsigned int p2Index = meshPart.triangleIndices[j*3+2];
assert(p0Index < (unsigned int)mesh.vertices.size()); assert(p0Index < (unsigned int)mesh.vertices.size());
assert(p1Index < (unsigned int)mesh.vertices.size()); assert(p1Index < (unsigned int)mesh.vertices.size());
assert(p2Index < (unsigned int)mesh.vertices.size()); assert(p2Index < (unsigned int)mesh.vertices.size());
glm::vec3 p0 = mesh.vertices[p0Index]; glm::vec3 p0 = mesh.vertices[p0Index];
glm::vec3 p1 = mesh.vertices[p1Index]; glm::vec3 p1 = mesh.vertices[p1Index];
glm::vec3 p2 = mesh.vertices[p2Index]; glm::vec3 p2 = mesh.vertices[p2Index];
aaBox += p0; aaBox += p0;
aaBox += p1; aaBox += p1;
aaBox += p2; aaBox += p2;
if (!pointsInPart.contains(p0)) { if (!pointsInPart.contains(p0)) {
pointsInPart << p0; pointsInPart << p0;
} }
@ -351,8 +352,44 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
} }
} }
// 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; QVector<glm::vec3> newMeshPoints;
_points << newMeshPoints; _points << newMeshPoints;
// add points to the new convex hull
_points[i++] << pointsInPart; _points[i++] << pointsInPart;
} }
} }
@ -362,7 +399,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
aaBoxDim = glm::clamp(aaBoxDim, glm::vec3(FLT_EPSILON), aaBoxDim); aaBoxDim = glm::clamp(aaBoxDim, glm::vec3(FLT_EPSILON), aaBoxDim);
glm::vec3 scale = _dimensions / aaBoxDim; glm::vec3 scale = _dimensions / aaBoxDim;
// multiply each point by scale before handing the point-set off to the physics engine // 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 i = 0; i < _points.size(); i++) {
for (int j = 0; j < _points[i].size(); j++) { for (int j = 0; j < _points[i].size(); j++) {

View file

@ -13,7 +13,7 @@
#include "FSTReader.h" #include "FSTReader.h"
QVariantHash parseMapping(QIODevice* device) { QVariantHash FSTReader::parseMapping(QIODevice* device) {
QVariantHash properties; QVariantHash properties;
QByteArray line; QByteArray line;
@ -48,13 +48,13 @@ QVariantHash parseMapping(QIODevice* device) {
return properties; return properties;
} }
QVariantHash readMapping(const QByteArray& data) { QVariantHash FSTReader::readMapping(const QByteArray& data) {
QBuffer buffer(const_cast<QByteArray*>(&data)); QBuffer buffer(const_cast<QByteArray*>(&data));
buffer.open(QIODevice::ReadOnly); 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() + " = "; QByteArray key = it.key().toUtf8() + " = ";
QVariantHash hashValue = it.value().toHash(); QVariantHash hashValue = it.value().toHash();
if (hashValue.isEmpty()) { if (hashValue.isEmpty()) {
@ -76,8 +76,8 @@ void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
} }
} }
QByteArray writeMapping(const QVariantHash& mapping) { QByteArray FSTReader::writeMapping(const QVariantHash& mapping) {
static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << SCALE_FIELD << FILENAME_FIELD static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD
<< TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD << TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD
<< BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD;
QBuffer buffer; QBuffer buffer;
@ -96,4 +96,76 @@ QByteArray writeMapping(const QVariantHash& mapping) {
} }
} }
return buffer.data(); 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;
}

View file

@ -12,9 +12,11 @@
#ifndef hifi_FSTReader_h #ifndef hifi_FSTReader_h
#define hifi_FSTReader_h #define hifi_FSTReader_h
#include <QBuffer>
#include <QVariantHash> #include <QVariantHash>
static const QString NAME_FIELD = "name"; static const QString NAME_FIELD = "name";
static const QString TYPE_FIELD = "type";
static const QString FILENAME_FIELD = "filename"; static const QString FILENAME_FIELD = "filename";
static const QString TEXDIR_FIELD = "texdir"; static const QString TEXDIR_FIELD = "texdir";
static const QString LOD_FIELD = "lod"; 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 FREE_JOINT_FIELD = "freeJoint";
static const QString BLENDSHAPE_FIELD = "bs"; static const QString BLENDSHAPE_FIELD = "bs";
/// Reads an FST mapping from the supplied data. class FSTReader {
QVariantHash readMapping(const QByteArray& data); public:
/// Writes an FST mapping to a byte array. enum ModelType {
QByteArray writeMapping(const QVariantHash& mapping); 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 #endif // hifi_FSTReader_h

48
libraries/physics/src/CharacterController.cpp Normal file → Executable file
View file

@ -274,7 +274,6 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher()); collisionWorld->getDispatcher()->dispatchAllCollisionPairs(_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
_currentPosition = _ghostObject->getWorldTransform().getOrigin(); _currentPosition = _ghostObject->getWorldTransform().getOrigin();
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
btVector3 currentPosition = _currentPosition; btVector3 currentPosition = _currentPosition;
@ -308,7 +307,7 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
btVector3 normal = pt.m_normalWorldOnB; btVector3 normal = pt.m_normalWorldOnB;
normal *= directionSign; // always points from object to character normal *= directionSign; // always points from object to character
btScalar normalDotUp = normal.dot(up); btScalar normalDotUp = normal.dot(_currentUp);
if (normalDotUp < _maxSlopeCosine) { if (normalDotUp < _maxSlopeCosine) {
// this contact has a non-vertical normal... might need to ignored // this contact has a non-vertical normal... might need to ignored
btVector3 collisionPoint; btVector3 collisionPoint;
@ -319,9 +318,9 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
} }
// we do math in frame where character base is origin // we do math in frame where character base is origin
btVector3 characterBase = currentPosition - (_radius + _halfHeight) * up; btVector3 characterBase = currentPosition - (_radius + _halfHeight) * _currentUp;
collisionPoint -= characterBase; collisionPoint -= characterBase;
btScalar collisionHeight = collisionPoint.dot(up); btScalar collisionHeight = collisionPoint.dot(_currentUp);
if (collisionHeight < _lastStepUp) { if (collisionHeight < _lastStepUp) {
// This contact is below the lastStepUp, so we ignore it for penetration resolution, // This contact is below the lastStepUp, so we ignore it for penetration resolution,
@ -357,11 +356,10 @@ void CharacterController::scanDown(btCollisionWorld* world) {
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
btVector3 start = _currentPosition; btVector3 start = _currentPosition;
const btScalar MAX_SCAN_HEIGHT = 20.0f + _halfHeight + _radius; // closest possible floor for disabling hover 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 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); world->rayTest(start, end, callback);
if (!callback.hasHit()) { if (!callback.hasHit()) {
@ -377,15 +375,14 @@ void CharacterController::stepUp(btCollisionWorld* world) {
// compute start and end // compute start and end
btTransform start, end; btTransform start, end;
start.setIdentity(); start.setIdentity();
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); start.setOrigin(_currentPosition + _currentUp * (_convexShape->getMargin() + _addedMargin));
start.setOrigin(_currentPosition + up * (_convexShape->getMargin() + _addedMargin));
_targetPosition = _currentPosition + up * _stepUpHeight; _targetPosition = _currentPosition + _currentUp * _stepUpHeight;
end.setIdentity(); end.setIdentity();
end.setOrigin(_targetPosition); end.setOrigin(_targetPosition);
// sweep up // sweep up
btVector3 sweepDirNegative = - up; btVector3 sweepDirNegative = - _currentUp;
btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.7071)); btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.7071));
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
@ -397,7 +394,7 @@ void CharacterController::stepUp(btCollisionWorld* world) {
_verticalOffset = 0.0f; _verticalOffset = 0.0f;
// Only modify the position if the hit was a slope and not a wall or ceiling. // 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; _lastStepUp = _stepUpHeight * callback.m_closestHitFraction;
_currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction); _currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction);
} else { } else {
@ -469,8 +466,8 @@ void CharacterController::stepForward(btCollisionWorld* collisionWorld, const bt
// sweep forward // sweep forward
btVector3 sweepDirNegative(_currentPosition - _targetPosition); btVector3 sweepDirNegative(_currentPosition - _targetPosition);
btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.0)); btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.0));
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
_ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); _ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
if (callback.hasHit()) { if (callback.hasHit()) {
@ -502,17 +499,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt
// reach of the character's feet. // reach of the character's feet.
// first sweep for ledge // first sweep for ledge
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); btVector3 step = (_verticalVelocity * dt - _lastStepUp) * _currentUp;
btVector3 step = (_verticalVelocity * dt - _lastStepUp) * up;
StepDownConvexResultCallback callback(_ghostObject, StepDownConvexResultCallback callback(_ghostObject,
up, _currentUp,
_currentPosition, step, _currentPosition, step,
_walkDirection, _walkDirection,
_maxSlopeCosine, _maxSlopeCosine,
_radius, _halfHeight); _radius, _halfHeight);
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
btTransform start, end; btTransform start, end;
start.setIdentity(); start.setIdentity();
@ -532,16 +528,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt
_isOnGround = true; _isOnGround = true;
} else if (!_isJumping) { } else if (!_isJumping) {
// sweep again for floor within downStep threshold // sweep again for floor within downStep threshold
step = -_stepDownHeight * up; step = -_stepDownHeight * _currentUp;
StepDownConvexResultCallback callback2 (_ghostObject, StepDownConvexResultCallback callback2 (_ghostObject,
up, _currentUp,
_currentPosition, step, _currentPosition, step,
_walkDirection, _walkDirection,
_maxSlopeCosine, _maxSlopeCosine,
_radius, _halfHeight); _radius, _halfHeight);
callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback2.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; callback2.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
_currentPosition = _targetPosition; _currentPosition = _targetPosition;
_targetPosition = _currentPosition + step; _targetPosition = _currentPosition + step;
@ -617,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(); const btTransform& transform = _ghostObject->getWorldTransform();
_currentRotation = transform.getRotation();
_currentPosition = transform.getOrigin(); _currentPosition = transform.getOrigin();
_targetPosition = _currentPosition;
} }
void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) { void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) {
@ -868,6 +864,7 @@ void CharacterController::updateShapeIfNecessary() {
void CharacterController::preSimulation(btScalar timeStep) { void CharacterController::preSimulation(btScalar timeStep) {
if (_enabled && _dynamicsWorld) { if (_enabled && _dynamicsWorld) {
glm::quat rotation = _avatarData->getOrientation(); glm::quat rotation = _avatarData->getOrientation();
btVector3 _currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset; glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset;
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity()); btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
@ -895,8 +892,7 @@ void CharacterController::postSimulation() {
// cap the velocity of the step so that the character doesn't POP! so hard on steps // cap the velocity of the step so that the character doesn't POP! so hard on steps
glm::vec3 finalStep = position - _lastPosition; glm::vec3 finalStep = position - _lastPosition;
btVector3 finalVelocity = _walkDirection; btVector3 finalVelocity = _walkDirection;
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); finalVelocity += _verticalVelocity * _currentUp;
finalVelocity += _verticalVelocity * up;
const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec
btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt; btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt;
btScalar stepLength = glm::length(finalStep); btScalar stepLength = glm::length(finalStep);

View file

@ -42,9 +42,22 @@ class btPairCachingGhostObject;
ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface
{ {
protected: 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; 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 btConvexShape* _convexShape;//is also in _ghostObject, but it needs to be convex, so we store it here to avoid upcast
btScalar _radius; btScalar _radius;
@ -64,22 +77,12 @@ protected:
btScalar _addedMargin;//@todo: remove this and fix the code 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; btScalar _lastStepUp;
///keep track of the contact manifolds ///keep track of the contact manifolds
btManifoldArray _manifoldArray; btManifoldArray _manifoldArray;
bool _touchingContact; bool _touchingContact;
btVector3 _floorNormal; // points from object to character
bool _enabled; bool _enabled;
bool _isOnGround; bool _isOnGround;
@ -90,9 +93,6 @@ protected:
btScalar _stepDt; btScalar _stepDt;
uint32_t _pendingFlags; uint32_t _pendingFlags;
glm::vec3 _shapeLocalOffset;
glm::vec3 _boxScale; // used to compute capsule shape
btDynamicsWorld* _dynamicsWorld = NULL; btDynamicsWorld* _dynamicsWorld = NULL;
btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal); btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal);

View file

@ -36,8 +36,8 @@ PhysicsEngine::~PhysicsEngine() {
delete _collisionDispatcher; delete _collisionDispatcher;
delete _broadphaseFilter; delete _broadphaseFilter;
delete _constraintSolver; delete _constraintSolver;
// delete _dynamicsWorld; delete _dynamicsWorld;
// delete _ghostPairCallback; delete _ghostPairCallback;
} }
// begin EntitySimulation overrides // begin EntitySimulation overrides

View file

@ -2132,7 +2132,7 @@ void NetworkGeometry::downloadFinished(QNetworkReply* reply) {
QUrl url = reply->url(); QUrl url = reply->url();
if (url.path().toLower().endsWith(".fst")) { if (url.path().toLower().endsWith(".fst")) {
// it's a mapping file; parse it and get the mesh filename // it's a mapping file; parse it and get the mesh filename
_mapping = readMapping(reply->readAll()); _mapping = FSTReader::readMapping(reply->readAll());
reply->deleteLater(); reply->deleteLater();
QString filename = _mapping.value("filename").toString(); QString filename = _mapping.value("filename").toString();
if (filename.isNull()) { if (filename.isNull()) {

View file

@ -359,8 +359,12 @@ void Font::setupGL() {
// FIXME there has to be a cleaner way of doing this // FIXME there has to be a cleaner way of doing this
QStringList Font::tokenizeForWrapping(const QString & str) const { QStringList Font::tokenizeForWrapping(const QString & str) const {
QStringList result; QStringList result;
foreach(const QString & token1, str.split(" ", QString::SkipEmptyParts)) { foreach(const QString & token1, str.split(" ")) {
bool lineFeed = false; bool lineFeed = false;
if (token1.isEmpty()) {
result << token1;
continue;
}
foreach(const QString & token2, token1.split("\n")) { foreach(const QString & token2, token1.split("\n")) {
if (lineFeed) { if (lineFeed) {
result << "\n"; result << "\n";

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

View 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

View file

@ -34,6 +34,7 @@
#include "EventTypes.h" #include "EventTypes.h"
#include "MenuItemProperties.h" #include "MenuItemProperties.h"
#include "ScriptAudioInjector.h" #include "ScriptAudioInjector.h"
#include "ScriptCache.h"
#include "ScriptEngine.h" #include "ScriptEngine.h"
#include "TypedArrays.h" #include "TypedArrays.h"
#include "XMLHttpRequestClass.h" #include "XMLHttpRequestClass.h"
@ -275,31 +276,26 @@ void ScriptEngine::loadURL(const QUrl& scriptURL) {
_scriptContents = in.readAll(); _scriptContents = in.readAll();
emit scriptLoaded(_fileNameString); emit scriptLoaded(_fileNameString);
} else { } else {
qDebug() << "ERROR Loading file:" << _fileNameString; qDebug() << "ERROR Loading file:" << _fileNameString << "line:" << __LINE__;
emit errorLoadingScript(_fileNameString); emit errorLoadingScript(_fileNameString);
} }
} else { } else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); bool isPending;
QNetworkRequest networkRequest = QNetworkRequest(url); auto scriptCache = DependencyManager::get<ScriptCache>();
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); scriptCache->getScript(url, this, isPending);
QNetworkReply* reply = networkAccessManager.get(networkRequest);
connect(reply, &QNetworkReply::finished, this, &ScriptEngine::handleScriptDownload);
} }
} }
} }
void ScriptEngine::handleScriptDownload() { void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); _scriptContents = scriptContents;
emit scriptLoaded(_fileNameString);
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { }
_scriptContents = reply->readAll();
emit scriptLoaded(_fileNameString); void ScriptEngine::errorInLoadingScript(const QUrl& url) {
} else { qDebug() << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
qDebug() << "ERROR Loading file:" << reply->url().toString(); emit errorLoadingScript(_fileNameString); // ??
emit errorLoadingScript(_fileNameString);
}
reply->deleteLater();
} }
void ScriptEngine::init() { void ScriptEngine::init() {
@ -765,7 +761,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
for (QUrl url : urls) { for (QUrl url : urls) {
QString contents = data[url]; QString contents = data[url];
if (contents.isNull()) { if (contents.isNull()) {
qDebug() << "Error loading file: " << url; qDebug() << "Error loading file: " << url << "line:" << __LINE__;
} else { } else {
QScriptValue result = evaluate(contents, url.toString()); QScriptValue result = evaluate(contents, url.toString());
} }

View file

@ -28,6 +28,7 @@
#include "ArrayBufferClass.h" #include "ArrayBufferClass.h"
#include "AudioScriptingInterface.h" #include "AudioScriptingInterface.h"
#include "Quat.h" #include "Quat.h"
#include "ScriptCache.h"
#include "ScriptUUID.h" #include "ScriptUUID.h"
#include "Vec3.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); 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 Q_OBJECT
public: public:
ScriptEngine(const QString& scriptContents = NO_SCRIPT, ScriptEngine(const QString& scriptContents = NO_SCRIPT,
@ -94,6 +95,9 @@ public:
void waitTillDoneRunning(); void waitTillDoneRunning();
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
virtual void errorInLoadingScript(const QUrl& url);
public slots: public slots:
void loadURL(const QUrl& scriptURL); void loadURL(const QUrl& scriptURL);
void stop(); void stop();
@ -160,8 +164,6 @@ private:
ArrayBufferClass* _arrayBufferClass; ArrayBufferClass* _arrayBufferClass;
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers; QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
private slots:
void handleScriptDownload();
private: private:
static QSet<ScriptEngine*> _allKnownScriptEngines; static QSet<ScriptEngine*> _allKnownScriptEngines;

View file

@ -14,6 +14,7 @@
#define hifi_Extents_h #define hifi_Extents_h
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtx/extented_min_max.hpp>
#include <QDebug> #include <QDebug>
#include "StreamUtils.h" #include "StreamUtils.h"
@ -46,6 +47,9 @@ public:
/// rotate the extents around orign by rotation /// rotate the extents around orign by rotation
void rotate(const glm::quat& 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 /// \return new Extents which is original rotated around orign by rotation
Extents getRotated(const glm::quat& rotation) const { Extents getRotated(const glm::quat& rotation) const {
Extents temp = { minimum, maximum }; Extents temp = { minimum, maximum };
@ -68,4 +72,4 @@ inline QDebug operator<<(QDebug debug, const Extents& extents) {
} }
#endif // hifi_Extents_h #endif // hifi_Extents_h

View file

@ -143,4 +143,12 @@ QDebug& operator<<(QDebug& dbg, const glm::mat4& m) {
return dbg << " ]}"; 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 #endif // QT_NO_DEBUG_STREAM

View file

@ -15,6 +15,7 @@
#include <iostream> #include <iostream>
#include <QByteArray> #include <QByteArray>
#include <QVariantHash>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtx/quaternion.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::vec4& v);
QDebug& operator<<(QDebug& s, const glm::quat& q); QDebug& operator<<(QDebug& s, const glm::quat& q);
QDebug& operator<<(QDebug& s, const glm::mat4& m); QDebug& operator<<(QDebug& s, const glm::mat4& m);
QDebug& operator<<(QDebug& dbg, const QVariantHash& v);
#endif // QT_NO_DEBUG_STREAM #endif // QT_NO_DEBUG_STREAM
#endif // hifi_StreamUtils_h #endif // hifi_StreamUtils_h

View file

@ -44,7 +44,13 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re
int count = 0; int count = 0;
foreach(FBXMesh mesh, geometry.meshes) { foreach(FBXMesh mesh, geometry.meshes) {
//get vertices for each mesh //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 //get the triangle indices for each mesh
QVector<int> triangles; QVector<int> triangles;
@ -57,8 +63,15 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re
if (triangles.count() <= 0){ if (triangles.count() <= 0){
continue; continue;
} }
AABox aaBox;
foreach (glm::vec3 p, vertices) {
aaBox += p;
}
results->perMeshVertices.append(vertices); results->perMeshVertices.append(vertices);
results->perMeshTriangleIndices.append(triangles); results->perMeshTriangleIndices.append(triangles);
results->perMeshLargestDimension.append(aaBox.getLargestDimension());
count++; count++;
} }
@ -66,23 +79,117 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re
return true; 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(); VHACD::IVHACD * interfaceVHACD = VHACD::CreateVHACD();
int meshCount = meshes->meshCount; int meshCount = meshes->meshCount;
int count = 0; int count = 0;
std::cout << "Performing V-HACD computation on " << meshCount << " meshes ..... " << std::endl; 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<glm::vec3> vertices = meshes->perMeshVertices.at(i).toStdVector();
std::vector<int> triangles = meshes->perMeshTriangleIndices.at(i).toStdVector(); std::vector<int> triangles = meshes->perMeshTriangleIndices.at(i).toStdVector();
int nPoints = (unsigned int)vertices.size(); int nPoints = (unsigned int)vertices.size();
int nTriangles = (unsigned int)triangles.size() / 3; 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 // compute approximate convex decomposition
bool res = interfaceVHACD->Compute(&vertices[0].x, 3, nPoints, &triangles[0], 3, nTriangles, params); bool res = interfaceVHACD->Compute(&vertices[0].x, 3, nPoints, &triangles[0], 3, nTriangles, params);
if (!res){ if (!res){
std::cout << "V-HACD computation failed for Mesh : " << i + 1 << std::endl; qDebug() << "V-HACD computation failed for Mesh : " << i;
continue; continue;
} }
count++; //For counting number of successfull computations 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 ]; m_triangles_copy[ i ] = hull.m_triangles[ i ];
} }
hull.m_triangles = m_triangles_copy; hull.m_triangles = m_triangles_copy;
convexHulls.append(hull); convexHulls.append(hull);
} }
results->convexHullList.append(convexHulls); results->convexHullList.append(convexHulls);

View file

@ -34,12 +34,16 @@ namespace vhacd {
int meshCount; int meshCount;
QVector<QVector<glm::vec3>> perMeshVertices; QVector<QVector<glm::vec3>> perMeshVertices;
QVector<QVector<int>> perMeshTriangleIndices; QVector<QVector<int>> perMeshTriangleIndices;
QVector<float> perMeshLargestDimension;
} LoadFBXResults; } LoadFBXResults;
class VHACDUtil { class VHACDUtil {
public: public:
bool loadFBX(const QString filename, vhacd::LoadFBXResults *results); 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(); ~VHACDUtil();
}; };

View file

@ -98,6 +98,15 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
const QCommandLineOption outputFilenameOption("o", "output file", "filename.obj"); const QCommandLineOption outputFilenameOption("o", "output file", "filename.obj");
parser.addOption(outputFilenameOption); 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())) { if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl; qCritical() << parser.errorText() << endl;
@ -138,13 +147,31 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
Q_UNREACHABLE(); 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 //set parameters for V-HACD
params.m_callback = &pCallBack; //progress callback params.m_callback = &pCallBack; //progress callback
params.m_resolution = 100000; // 100000 params.m_resolution = 100000; // 100000
params.m_depth = 20; // 20 params.m_depth = 20; // 20
params.m_concavity = 0.001; // 0.001 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_planeDownsampling = 4; // 4
params.m_convexhullDownsampling = 4; // 4 params.m_convexhullDownsampling = 4; // 4
params.m_alpha = 0.05; // 0.05 // controls the bias toward clipping along symmetry planes 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_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_mode = 0; // 0: voxel-based (recommended), 1: tetrahedron-based
params.m_maxNumVerticesPerCH = 64; // 64 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_callback = 0; // 0
params.m_logger = 0; // 0 params.m_logger = 0; // 0
params.m_convexhullApproximation = true; // true params.m_convexhullApproximation = true; // true
@ -172,7 +199,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
begin = std::chrono::high_resolution_clock::now(); 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..."; cout << "Compute Failed...";
} }
end = std::chrono::high_resolution_clock::now(); end = std::chrono::high_resolution_clock::now();