// NOTE: Random guy on the internet's recommended triangle slices //Triangle tri1 = { v0, v1, v2 }; //Triangle tri2 = { v2, v3, v0 }; thisMeshTriangles.push_back(tri1); thisMeshTriangles.push_back(tri2); } } if (part.triangleIndices.size() > 0) { int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; int vIndex = 0; for (int t = 0; t < numberOfTris; t++) { int i0 = part.triangleIndices[vIndex++]; int i1 = part.triangleIndices[vIndex++]; int i2 = part.triangleIndices[vIndex++]; glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); // track the mesh parts in model space if (!atLeastOnePointInBounds) { thisPartBounds.setBox(mv0, 0.0f); atLeastOnePointInBounds = true; } else { thisPartBounds += mv0; } thisPartBounds += mv1; thisPartBounds += mv2; glm::vec3 v0 = calculateScaledOffsetPoint(mv0); glm::vec3 v1 = calculateScaledOffsetPoint(mv1); glm::vec3 v2 = calculateScaledOffsetPoint(mv2); Triangle tri = { v0, v1, v2 }; thisMeshTriangles.push_back(tri); } } _calculatedMeshPartBoxes[QPair<int,int>(i, j)] = thisPartBounds; } _calculatedMeshTriangles[i] = thisMeshTriangles; _calculatedMeshPartBoxesValid = true; } } _calculatedMeshBoxesValid = true; _calculatedMeshTrianglesValid = pickAgainstTriangles; }} void Model::renderSetup(RenderArgs* args) { // set up dilated textures on first render after load/simulate const FBXGeometry& geometry = getFBXGeometry(); if (_dilatedTextures.isEmpty()) { foreach (const FBXMesh& mesh, geometry.meshes) { QVector<QSharedPointer<Texture> > dilated; dilated.resize(mesh.parts.size()); _dilatedTextures.append(dilated); } } if (!_meshGroupsKnown && isLoaded()) { segregateMeshGroups(); }} void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene) { if (_isVisible != newValue) { _isVisible = newValue; render::PendingChanges pendingChanges; foreach (auto item, _modelMeshRenderItems.keys()) { pendingChanges.resetItem(item, _modelMeshRenderItems[item]); } foreach (auto item, _collisionRenderItems.keys()) { pendingChanges.resetItem(item, _collisionRenderItems[item]); } scene->enqueuePendingChanges(pendingChanges); }} bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters, bool showCollisionHull) { if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { _showCollisionHull = showCollisionHull; segregateMeshGroups(); } bool somethingAdded = false; if (_modelMeshRenderItems.empty()) { foreach (auto renderItem, _modelMeshRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem); if (statusGetters.size()) { renderPayload->addStatusGetters(statusGetters); } pendingChanges.resetItem(item, renderPayload); _modelMeshRenderItems.insert(item, renderPayload); somethingAdded = true; } } if (_collisionRenderItems.empty()) { foreach (auto renderItem, _collisionRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem); if (statusGetters.size()) { renderPayload->addStatusGetters(statusGetters); } pendingChanges.resetItem(item, renderPayload); _collisionRenderItems.insert(item, renderPayload); somethingAdded = true; } } updateRenderItems(); _readyWhenAdded = readyToAddToScene(); return somethingAdded;} void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) { foreach (auto item, _modelMeshRenderItems.keys()) { pendingChanges.removeItem(item); } _modelMeshRenderItems.clear(); _modelMeshRenderItemsSet.clear(); foreach (auto item, _collisionRenderItems.keys()) { pendingChanges.removeItem(item); } _collisionRenderItems.clear(); _collisionRenderItemsSet.clear(); _meshGroupsKnown = false; _readyWhenAdded = false;} void Model::renderDebugMeshBoxes(gpu::Batch& batch) { int colorNdx = 0; _mutex.lock(); foreach(AABox box, _calculatedMeshBoxes) { if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) { _debugMeshBoxesID = DependencyManager::get<GeometryCache>()->allocateID(); } QVector<glm::vec3> points; glm::vec3 brn = box.getCorner(); glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0); glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z); glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z); glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0); glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0); glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0); glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0); points << brn << bln; points << brf << blf; points << brn << brf; points << bln << blf; points << trn << tln; points << trf << tlf; points << trn << trf; points << tln << tlf; points << brn << trn; points << brf << trf; points << bln << tln; points << blf << tlf; glm::vec4 color[] = { { 1.0f, 0.0f, 0.0f, 1.0f }, // red { 0.0f, 1.0f, 0.0f, 1.0f }, // green { 0.0f, 0.0f, 1.0f, 1.0f }, // blue { 1.0f, 0.0f, 1.0f, 1.0f }, // purple { 1.0f, 1.0f, 0.0f, 1.0f }, // yellow { 0.0f, 1.0f, 1.0f, 1.0f }, // cyan { 1.0f, 1.0f, 1.0f, 1.0f }, // white { 0.0f, 0.5f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.5f, 1.0f }, { 0.5f, 0.0f, 0.5f, 1.0f }, { 0.5f, 0.5f, 0.0f, 1.0f }, { 0.0f, 0.5f, 0.5f, 1.0f } }; DependencyManager::get<GeometryCache>()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]); DependencyManager::get<GeometryCache>()->renderVertices(batch, gpu::LINES, _debugMeshBoxesID); colorNdx++; } _mutex.unlock();} Extents Model::getBindExtents() const { if (!isActive()) { return Extents(); } const Extents& bindExtents = getFBXGeometry().bindExtents; Extents scaledExtents = { bindExtents.minimum * _scale, bindExtents.maximum * _scale }; return scaledExtents;} Extents Model::getMeshExtents() const { if (!isActive()) { return Extents(); } const Extents& extents = getFBXGeometry().meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum * _scale, maximum * _scale }; return scaledExtents;} Extents Model::getUnscaledMeshExtents() const { if (!isActive()) { return Extents(); } const Extents& extents = getFBXGeometry().meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum, maximum }; return scaledExtents;} Extents Model::calculateScaledOffsetExtents(const Extents& extents, glm::vec3 modelPosition, glm::quat modelOrientation) const { // we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledOffsetExtents = { ((minimum + _offset) * _scale), ((maximum + _offset) * _scale) }; Extents rotatedExtents = scaledOffsetExtents.getRotated(modelOrientation); Extents translatedExtents = { rotatedExtents.minimum + modelPosition, rotatedExtents.maximum + modelPosition }; return translatedExtents;} /// Returns the world space equivalent of some box in model space.AABox Model::calculateScaledOffsetAABox(const AABox& box, glm::vec3 modelPosition, glm::quat modelOrientation) const { return AABox(calculateScaledOffsetExtents(Extents(box), modelPosition, modelOrientation));} glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { // we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix glm::vec3 offsetPoint = glm::vec3(getFBXGeometry().offset * glm::vec4(point, 1.0f)); glm::vec3 scaledPoint = ((offsetPoint + _offset) * _scale); glm::vec3 rotatedPoint = _rotation * scaledPoint; glm::vec3 translatedPoint = rotatedPoint + _translation; return translatedPoint;} void Model::clearJointState(int index) { _rig->clearJointState(index);} void Model::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { _rig->setJointState(index, valid, rotation, translation, priority);} void Model::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { _rig->setJointRotation(index, valid, rotation, priority);} void Model::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { _rig->setJointTranslation(index, valid, translation, priority);} int Model::getParentJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).parentIndex : -1;} int Model::getLastFreeJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;} void Model::setTextures(const QVariantMap& textures) { if (isLoaded()) { _needsUpdateTextures = true; _geometry->getGeometry()->setTextures(textures); }} void Model::setURL(const QUrl& url) { // don't recreate the geometry if it's the same URL if (_url == url && _geometry && _geometry->getURL() == url) { return; } _url = url; { render::PendingChanges pendingChanges; render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); removeFromScene(scene, pendingChanges); scene->enqueuePendingChanges(pendingChanges); } _needsReload = true; _needsUpdateTextures = true; _meshGroupsKnown = false; invalidCalculatedMeshBoxes(); deleteGeometry(); _geometry = DependencyManager::get<ModelCache>()->getGeometry(url); onInvalidate();} void Model::setCollisionModelURL(const QUrl& url) { if (_collisionUrl == url) { return; } _collisionUrl = url; _collisionGeometry = DependencyManager::get<ModelCache>()->getGeometry(url);} bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);} bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { return _rig->getJointPosition(jointIndex, position);} bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const { return _rig->getJointRotationInWorldFrame(jointIndex, rotation, _rotation);} bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointRotation(jointIndex, rotation);} bool Model::getJointTranslation(int jointIndex, glm::vec3& translation) const { return _rig->getJointTranslation(jointIndex, translation);} bool Model::getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotationOut) const { return _rig->getAbsoluteJointRotationInRigFrame(jointIndex, rotationOut);} bool Model::getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translationOut) const { return _rig->getAbsoluteJointTranslationInRigFrame(jointIndex, translationOut);} bool Model::getRelativeDefaultJointRotation(int jointIndex, glm::quat& rotationOut) const { return _rig->getRelativeDefaultJointRotation(jointIndex, rotationOut);} bool Model::getRelativeDefaultJointTranslation(int jointIndex, glm::vec3& translationOut) const { return _rig->getRelativeDefaultJointTranslation(jointIndex, translationOut);} bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation);} QStringList Model::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; QMetaObject::invokeMethod(const_cast<Model*>(this), "getJointNames", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QStringList, result)); return result; } return isActive() ? getFBXGeometry().getJointNames() : QStringList();} class Blender : public QRunnable {public: Blender(ModelPointer model, int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry, const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients); virtual void run(); private: ModelPointer _model; int _blendNumber; std::weak_ptr<NetworkGeometry> _geometry; QVector<FBXMesh> _meshes; QVector<float> _blendshapeCoefficients;}; Blender::Blender(ModelPointer model, int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry, const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients) : _model(model), _blendNumber(blendNumber), _geometry(geometry), _meshes(meshes), _blendshapeCoefficients(blendshapeCoefficients) {} void Blender::run() { PROFILE_RANGE(__FUNCTION__); QVector<glm::vec3> vertices, normals; if (_model) { int offset = 0; foreach (const FBXMesh& mesh, _meshes) { if (mesh.blendshapes.isEmpty()) { continue; } vertices += mesh.vertices; normals += mesh.normals; glm::vec3* meshVertices = vertices.data() + offset; glm::vec3* meshNormals = normals.data() + offset; offset += mesh.vertices.size(); const float NORMAL_COEFFICIENT_SCALE = 0.01f; for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { float vertexCoefficient = _blendshapeCoefficients.at(i); const float EPSILON = 0.0001f; if (vertexCoefficient < EPSILON) { continue; } float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; const FBXBlendshape& blendshape = mesh.blendshapes.at(i); for (int j = 0; j < blendshape.indices.size(); j++) { int index = blendshape.indices.at(j); meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; } } } } // post the result to the geometry cache, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get<ModelBlender>().data(), "setBlendedVertices", Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(const std::weak_ptr<NetworkGeometry>&, _geometry), Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));} void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { if (_scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) { _scaleToFit = scaleToFit; _scaleToFitDimensions = dimensions; _scaledToFit = false; // force rescaling }} void Model::setScaleToFit(bool scaleToFit, float largestDimension, bool forceRescale) { // NOTE: if the model is not active, then it means we don't actually know the true/natural dimensions of the // mesh, and so we can't do the needed calculations for scaling to fit to a single largest dimension. In this // case we will record that we do want to do this, but we will stick our desired single dimension into the // first element of the vec3 for the non-fixed aspect ration dimensions if (!isActive()) { _scaleToFit = scaleToFit; if (scaleToFit) { _scaleToFitDimensions = glm::vec3(largestDimension, FAKE_DIMENSION_PLACEHOLDER, FAKE_DIMENSION_PLACEHOLDER); } return; } if (forceRescale || _scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) { _scaleToFit = scaleToFit; // we only need to do this work if we're "turning on" scale to fit. if (scaleToFit) { Extents modelMeshExtents = getUnscaledMeshExtents(); float maxDimension = glm::distance(modelMeshExtents.maximum, modelMeshExtents.minimum); float maxScale = largestDimension / maxDimension; glm::vec3 modelMeshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; glm::vec3 dimensions = modelMeshDimensions * maxScale; _scaleToFitDimensions = dimensions; _scaledToFit = false; // force rescaling } }} glm::vec3 Model::getScaleToFitDimensions() const { if (_scaleToFitDimensions.y == FAKE_DIMENSION_PLACEHOLDER && _scaleToFitDimensions.z == FAKE_DIMENSION_PLACEHOLDER) { return glm::vec3(_scaleToFitDimensions.x); } return _scaleToFitDimensions;} void Model::scaleToFit() { // If our _scaleToFitDimensions.y/z are FAKE_DIMENSION_PLACEHOLDER then it means our // user asked to scale us in a fixed aspect ratio to a single largest dimension, but // we didn't yet have an active mesh. We can only enter this scaleToFit() in this state // if we now do have an active mesh, so we take this opportunity to actually determine // the correct scale. if (_scaleToFit && _scaleToFitDimensions.y == FAKE_DIMENSION_PLACEHOLDER && _scaleToFitDimensions.z == FAKE_DIMENSION_PLACEHOLDER) { setScaleToFit(_scaleToFit, _scaleToFitDimensions.x);Lines 501–1000 of 1348. Previous lines Load more
// NOTE: Random guy on the internet's recommended triangle slices //Triangle tri1 = { v0, v1, v2 }; //Triangle tri2 = { v2, v3, v0 }; thisMeshTriangles.push_back(tri1); thisMeshTriangles.push_back(tri2); } } if (part.triangleIndices.size() > 0) { int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; int vIndex = 0; for (int t = 0; t < numberOfTris; t++) { int i0 = part.triangleIndices[vIndex++]; int i1 = part.triangleIndices[vIndex++]; int i2 = part.triangleIndices[vIndex++]; glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); // track the mesh parts in model space if (!atLeastOnePointInBounds) { thisPartBounds.setBox(mv0, 0.0f); atLeastOnePointInBounds = true; } else { thisPartBounds += mv0; } thisPartBounds += mv1; thisPartBounds += mv2; glm::vec3 v0 = calculateScaledOffsetPoint(mv0); glm::vec3 v1 = calculateScaledOffsetPoint(mv1); glm::vec3 v2 = calculateScaledOffsetPoint(mv2); Triangle tri = { v0, v1, v2 }; thisMeshTriangles.push_back(tri); } } _calculatedMeshPartBoxes[QPair<int,int>(i, j)] = thisPartBounds; } _calculatedMeshTriangles[i] = thisMeshTriangles; _calculatedMeshPartBoxesValid = true; } } _calculatedMeshBoxesValid = true; _calculatedMeshTrianglesValid = pickAgainstTriangles; } } void Model::renderSetup(RenderArgs* args) { // set up dilated textures on first render after load/simulate const FBXGeometry& geometry = getFBXGeometry(); if (_dilatedTextures.isEmpty()) { foreach (const FBXMesh& mesh, geometry.meshes) { QVector<QSharedPointer<Texture> > dilated; dilated.resize(mesh.parts.size()); _dilatedTextures.append(dilated); } } if (!_meshGroupsKnown && isLoaded()) { segregateMeshGroups(); } } void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene) { if (_isVisible != newValue) { _isVisible = newValue; render::PendingChanges pendingChanges; foreach (auto item, _modelMeshRenderItems.keys()) { pendingChanges.resetItem(item, _modelMeshRenderItems[item]); } foreach (auto item, _collisionRenderItems.keys()) { pendingChanges.resetItem(item, _collisionRenderItems[item]); } scene->enqueuePendingChanges(pendingChanges); } } bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters, bool showCollisionHull) { if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { _showCollisionHull = showCollisionHull; segregateMeshGroups(); } bool somethingAdded = false; if (_modelMeshRenderItems.empty()) { foreach (auto renderItem, _modelMeshRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem); if (statusGetters.size()) { renderPayload->addStatusGetters(statusGetters); } pendingChanges.resetItem(item, renderPayload); _modelMeshRenderItems.insert(item, renderPayload); somethingAdded = true; } } if (_collisionRenderItems.empty()) { foreach (auto renderItem, _collisionRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem); if (statusGetters.size()) { renderPayload->addStatusGetters(statusGetters); } pendingChanges.resetItem(item, renderPayload); _collisionRenderItems.insert(item, renderPayload); somethingAdded = true; } } updateRenderItems(); _readyWhenAdded = readyToAddToScene(); return somethingAdded; } void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) { foreach (auto item, _modelMeshRenderItems.keys()) { pendingChanges.removeItem(item); } _modelMeshRenderItems.clear(); _modelMeshRenderItemsSet.clear(); foreach (auto item, _collisionRenderItems.keys()) { pendingChanges.removeItem(item); } _collisionRenderItems.clear(); _collisionRenderItemsSet.clear(); _meshGroupsKnown = false; _readyWhenAdded = false; } void Model::renderDebugMeshBoxes(gpu::Batch& batch) { int colorNdx = 0; _mutex.lock(); foreach(AABox box, _calculatedMeshBoxes) { if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) { _debugMeshBoxesID = DependencyManager::get<GeometryCache>()->allocateID(); } QVector<glm::vec3> points; glm::vec3 brn = box.getCorner(); glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0); glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z); glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z); glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0); glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0); glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0); glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0); points << brn << bln; points << brf << blf; points << brn << brf; points << bln << blf; points << trn << tln; points << trf << tlf; points << trn << trf; points << tln << tlf; points << brn << trn; points << brf << trf; points << bln << tln; points << blf << tlf; glm::vec4 color[] = { { 1.0f, 0.0f, 0.0f, 1.0f }, // red { 0.0f, 1.0f, 0.0f, 1.0f }, // green { 0.0f, 0.0f, 1.0f, 1.0f }, // blue { 1.0f, 0.0f, 1.0f, 1.0f }, // purple { 1.0f, 1.0f, 0.0f, 1.0f }, // yellow { 0.0f, 1.0f, 1.0f, 1.0f }, // cyan { 1.0f, 1.0f, 1.0f, 1.0f }, // white { 0.0f, 0.5f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.5f, 1.0f }, { 0.5f, 0.0f, 0.5f, 1.0f }, { 0.5f, 0.5f, 0.0f, 1.0f }, { 0.0f, 0.5f, 0.5f, 1.0f } }; DependencyManager::get<GeometryCache>()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]); DependencyManager::get<GeometryCache>()->renderVertices(batch, gpu::LINES, _debugMeshBoxesID); colorNdx++; } _mutex.unlock(); } Extents Model::getBindExtents() const { if (!isActive()) { return Extents(); } const Extents& bindExtents = getFBXGeometry().bindExtents; Extents scaledExtents = { bindExtents.minimum * _scale, bindExtents.maximum * _scale }; return scaledExtents; } Extents Model::getMeshExtents() const { if (!isActive()) { return Extents(); } const Extents& extents = getFBXGeometry().meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum * _scale, maximum * _scale }; return scaledExtents; } Extents Model::getUnscaledMeshExtents() const { if (!isActive()) { return Extents(); } const Extents& extents = getFBXGeometry().meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum, maximum }; return scaledExtents; } Extents Model::calculateScaledOffsetExtents(const Extents& extents, glm::vec3 modelPosition, glm::quat modelOrientation) const { // we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledOffsetExtents = { ((minimum + _offset) * _scale), ((maximum + _offset) * _scale) }; Extents rotatedExtents = scaledOffsetExtents.getRotated(modelOrientation); Extents translatedExtents = { rotatedExtents.minimum + modelPosition, rotatedExtents.maximum + modelPosition }; return translatedExtents; } /// Returns the world space equivalent of some box in model space. AABox Model::calculateScaledOffsetAABox(const AABox& box, glm::vec3 modelPosition, glm::quat modelOrientation) const { return AABox(calculateScaledOffsetExtents(Extents(box), modelPosition, modelOrientation)); } glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { // we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix glm::vec3 offsetPoint = glm::vec3(getFBXGeometry().offset * glm::vec4(point, 1.0f)); glm::vec3 scaledPoint = ((offsetPoint + _offset) * _scale); glm::vec3 rotatedPoint = _rotation * scaledPoint; glm::vec3 translatedPoint = rotatedPoint + _translation; return translatedPoint; } void Model::clearJointState(int index) { _rig->clearJointState(index); } void Model::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { _rig->setJointState(index, valid, rotation, translation, priority); } void Model::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { _rig->setJointRotation(index, valid, rotation, priority); } void Model::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { _rig->setJointTranslation(index, valid, translation, priority); } int Model::getParentJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).parentIndex : -1; } int Model::getLastFreeJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1; } void Model::setTextures(const QVariantMap& textures) { if (isLoaded()) { _needsUpdateTextures = true; _geometry->getGeometry()->setTextures(textures); } } void Model::setURL(const QUrl& url) { // don't recreate the geometry if it's the same URL if (_url == url && _geometry && _geometry->getURL() == url) { return; } _url = url; { render::PendingChanges pendingChanges; render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); removeFromScene(scene, pendingChanges); scene->enqueuePendingChanges(pendingChanges); } _needsReload = true; _needsUpdateTextures = true; _meshGroupsKnown = false; invalidCalculatedMeshBoxes(); deleteGeometry(); _geometry = DependencyManager::get<ModelCache>()->getGeometry(url); onInvalidate(); } void Model::setCollisionModelURL(const QUrl& url) { if (_collisionUrl == url) { return; } _collisionUrl = url; _collisionGeometry = DependencyManager::get<ModelCache>()->getGeometry(url); } bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation); } bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { return _rig->getJointPosition(jointIndex, position); } bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const { return _rig->getJointRotationInWorldFrame(jointIndex, rotation, _rotation); } bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointRotation(jointIndex, rotation); } bool Model::getJointTranslation(int jointIndex, glm::vec3& translation) const { return _rig->getJointTranslation(jointIndex, translation); } bool Model::getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotationOut) const { return _rig->getAbsoluteJointRotationInRigFrame(jointIndex, rotationOut); } bool Model::getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translationOut) const { return _rig->getAbsoluteJointTranslationInRigFrame(jointIndex, translationOut); } bool Model::getRelativeDefaultJointRotation(int jointIndex, glm::quat& rotationOut) const { return _rig->getRelativeDefaultJointRotation(jointIndex, rotationOut); } bool Model::getRelativeDefaultJointTranslation(int jointIndex, glm::vec3& translationOut) const { return _rig->getRelativeDefaultJointTranslation(jointIndex, translationOut); } bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation); } QStringList Model::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; QMetaObject::invokeMethod(const_cast<Model*>(this), "getJointNames", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QStringList, result)); return result; } return isActive() ? getFBXGeometry().getJointNames() : QStringList(); } class Blender : public QRunnable { public: Blender(ModelPointer model, int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry, const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients); virtual void run(); private: ModelPointer _model; int _blendNumber; std::weak_ptr<NetworkGeometry> _geometry; QVector<FBXMesh> _meshes; QVector<float> _blendshapeCoefficients; }; Blender::Blender(ModelPointer model, int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry, const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients) : _model(model), _blendNumber(blendNumber), _geometry(geometry), _meshes(meshes), _blendshapeCoefficients(blendshapeCoefficients) { } void Blender::run() { PROFILE_RANGE(__FUNCTION__); QVector<glm::vec3> vertices, normals; if (_model) { int offset = 0; foreach (const FBXMesh& mesh, _meshes) { if (mesh.blendshapes.isEmpty()) { continue; } vertices += mesh.vertices; normals += mesh.normals; glm::vec3* meshVertices = vertices.data() + offset; glm::vec3* meshNormals = normals.data() + offset; offset += mesh.vertices.size(); const float NORMAL_COEFFICIENT_SCALE = 0.01f; for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { float vertexCoefficient = _blendshapeCoefficients.at(i); const float EPSILON = 0.0001f; if (vertexCoefficient < EPSILON) { continue; } float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; const FBXBlendshape& blendshape = mesh.blendshapes.at(i); for (int j = 0; j < blendshape.indices.size(); j++) { int index = blendshape.indices.at(j); meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; } } } } // post the result to the geometry cache, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get<ModelBlender>().data(), "setBlendedVertices", Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(const std::weak_ptr<NetworkGeometry>&, _geometry), Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals)); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { if (_scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) { _scaleToFit = scaleToFit; _scaleToFitDimensions = dimensions; _scaledToFit = false; // force rescaling } } void Model::setScaleToFit(bool scaleToFit, float largestDimension, bool forceRescale) { // NOTE: if the model is not active, then it means we don't actually know the true/natural dimensions of the // mesh, and so we can't do the needed calculations for scaling to fit to a single largest dimension. In this // case we will record that we do want to do this, but we will stick our desired single dimension into the // first element of the vec3 for the non-fixed aspect ration dimensions if (!isActive()) { _scaleToFit = scaleToFit; if (scaleToFit) { _scaleToFitDimensions = glm::vec3(largestDimension, FAKE_DIMENSION_PLACEHOLDER, FAKE_DIMENSION_PLACEHOLDER); } return; } if (forceRescale || _scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) { _scaleToFit = scaleToFit; // we only need to do this work if we're "turning on" scale to fit. if (scaleToFit) { Extents modelMeshExtents = getUnscaledMeshExtents(); float maxDimension = glm::distance(modelMeshExtents.maximum, modelMeshExtents.minimum); float maxScale = largestDimension / maxDimension; glm::vec3 modelMeshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; glm::vec3 dimensions = modelMeshDimensions * maxScale; _scaleToFitDimensions = dimensions; _scaledToFit = false; // force rescaling } } } glm::vec3 Model::getScaleToFitDimensions() const { if (_scaleToFitDimensions.y == FAKE_DIMENSION_PLACEHOLDER && _scaleToFitDimensions.z == FAKE_DIMENSION_PLACEHOLDER) { return glm::vec3(_scaleToFitDimensions.x); } return _scaleToFitDimensions; } void Model::scaleToFit() { // If our _scaleToFitDimensions.y/z are FAKE_DIMENSION_PLACEHOLDER then it means our // user asked to scale us in a fixed aspect ratio to a single largest dimension, but // we didn't yet have an active mesh. We can only enter this scaleToFit() in this state // if we now do have an active mesh, so we take this opportunity to actually determine // the correct scale. if (_scaleToFit && _scaleToFitDimensions.y == FAKE_DIMENSION_PLACEHOLDER && _scaleToFitDimensions.z == FAKE_DIMENSION_PLACEHOLDER) { setScaleToFit(_scaleToFit, _scaleToFitDimensions.x);