diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 2b65081185..2353a20e7d 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -61,7 +61,7 @@ void RenderableModelEntityItem::remapTextures() { } if (!_model->isLoadedWithTextures()) { - return; // nothing to do if the model has not yet loaded it's default textures + return; // nothing to do if the model has not yet loaded its default textures } if (!_originalTexturesRead && _model->isLoadedWithTextures()) { @@ -220,7 +220,7 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { // if we have a URL, then we will want to end up returning a model... if (!getModelURL().isEmpty()) { - // if we have a previously allocated model, but it's URL doesn't match + // if we have a previously allocated model, but its URL doesn't match // then we need to let our renderer update our model for us. if (_model && QUrl(getModelURL()) != _model->getURL()) { result = _model = _myRenderer->updateModel(_model, getModelURL(), getCollisionModelURL()); @@ -293,7 +293,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { } if (_model->getCollisionURL().isEmpty()) { - // no model url, so we're ready to compute a shape. + // no collision-model url, so we're ready to compute a shape (of type None). return true; } @@ -312,35 +312,30 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { info.setParams(getShapeType(), 0.5f * getDimensions()); } else { const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); - const FBXGeometry& fbxGeometry = collisionNetworkGeometry->getFBXGeometry(); + const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry(); + + const QSharedPointer renderNetworkGeometry = _model->getGeometry(); + const FBXGeometry& renderGeometry = renderNetworkGeometry->getFBXGeometry(); - AABox aaBox; _points.clear(); unsigned int i = 0; // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. - foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + foreach (const FBXMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull foreach (const FBXMeshPart &meshPart, mesh.parts) { QVector pointsInPart; // run through all the triangles and (uniquely) add each point to the hull unsigned int triangleCount = meshPart.triangleIndices.size() / 3; - assert((unsigned int)meshPart.triangleIndices.size() == triangleCount*3); for (unsigned int j = 0; j < triangleCount; j++) { unsigned int p0Index = meshPart.triangleIndices[j*3]; unsigned int p1Index = meshPart.triangleIndices[j*3+1]; unsigned int p2Index = meshPart.triangleIndices[j*3+2]; - assert(p0Index < (unsigned int)mesh.vertices.size()); - assert(p1Index < (unsigned int)mesh.vertices.size()); - assert(p2Index < (unsigned int)mesh.vertices.size()); glm::vec3 p0 = mesh.vertices[p0Index]; glm::vec3 p1 = mesh.vertices[p1Index]; glm::vec3 p2 = mesh.vertices[p2Index]; - aaBox += p0; - aaBox += p1; - aaBox += p2; if (!pointsInPart.contains(p0)) { pointsInPart << p0; } @@ -360,18 +355,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { 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; } @@ -386,6 +373,11 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } + if (pointsInPart.size() == 0) { + qDebug() << "Warning -- meshPart has no faces"; + continue; + } + // add next convex hull QVector newMeshPoints; _points << newMeshPoints; @@ -394,11 +386,14 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } - // make sure we aren't about to divide by zero - glm::vec3 aaBoxDim = aaBox.getDimensions(); - aaBoxDim = glm::clamp(aaBoxDim, glm::vec3(FLT_EPSILON), aaBoxDim); + // We expect that the collision model will have the same units and will be displaced + // from its origin in the same way the visual model is. The visual model has + // been centered and probably scaled. We take the scaling and offset which were applied + // to the visual model and apply them to the collision model (without regard for the + // collision model's extents). + + glm::vec3 scale = _dimensions / renderGeometry.getUnscaledMeshExtents().size(); - glm::vec3 scale = _dimensions / aaBoxDim; // multiply each point by scale before handing the point-set off to the physics engine for (int i = 0; i < _points.size(); i++) { for (int j = 0; j < _points[i].size(); j++) { diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index db2733f27b..c87a942baa 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -27,7 +27,8 @@ public: enum SpecialToken { NO_TOKEN = -1, NO_PUSHBACKED_TOKEN = -1, - DATUM_TOKEN = 0x100 + DATUM_TOKEN = 0x100, + COMMENT_TOKEN = 0x101 }; int nextToken(); const QByteArray& getDatum() const { return _datum; } @@ -35,11 +36,13 @@ public: void skipLine() { _device->readLine(); } void pushBackToken(int token) { _pushedBackToken = token; } void ungetChar(char ch) { _device->ungetChar(ch); } + const QString getComment() const { return _comment; } private: QIODevice* _device; QByteArray _datum; int _pushedBackToken; + QString _comment; }; @@ -56,9 +59,11 @@ int OBJTokenizer::nextToken() { continue; // skip whitespace } switch (ch) { - case '#': - _device->readLine(); // skip the comment - break; + case '#': { + _comment = _device->readLine(); // skip the comment + qDebug() << "COMMENT:" << _comment; + return COMMENT_TOKEN; + } case '\"': _datum = ""; @@ -104,7 +109,8 @@ bool OBJTokenizer::isNextTokenFloat() { } bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, - FBXGeometry &geometry, QVector& faceNormals, QVector& faceNormalIndexes) { + FBXGeometry &geometry, QVector& faceNormals, QVector& faceNormalIndexes, + float& scaleGuess) { FBXMesh &mesh = geometry.meshes[0]; mesh.parts.append(FBXMeshPart()); FBXMeshPart &meshPart = mesh.parts.last(); @@ -128,7 +134,17 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0)); while (true) { - if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { + int tokenType = tokenizer.nextToken(); + if (tokenType == OBJTokenizer::COMMENT_TOKEN) { + if (tokenizer.getComment().contains("This file uses centimeters as units")) { + scaleGuess = 1.0f / 100.0f; + } + if (tokenizer.getComment().contains("This file uses millimeters as units")) { + scaleGuess = 1.0f / 1000.0f; + } + continue; + } + if (tokenType != OBJTokenizer::DATUM_TOKEN) { result = false; break; } @@ -192,6 +208,7 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, while (true) { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { if (indices.count() == 0) { + // nonsense, bail out. goto done; } break; @@ -266,22 +283,22 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, } } else { // something we don't (yet) care about - qDebug() << "OBJ parser is skipping a line with" << token; + // qDebug() << "OBJ parser is skipping a line with" << token; tokenizer.skipLine(); } } done: + + if (meshPart.triangleIndices.size() == 0 && meshPart.quadIndices.size() == 0) { + // empty mesh? + mesh.parts.pop_back(); + } + return result; } -FBXGeometry extractOBJGeometry(const FBXNode& node, const QVariantHash& mapping) { - FBXGeometry geometry; - return geometry; -} - - FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); @@ -294,24 +311,30 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { OBJTokenizer tokenizer(device); QVector faceNormalIndexes; QVector faceNormals; + float scaleGuess = 1.0f; faceNormalIndexes.clear(); geometry.meshExtents.reset(); geometry.meshes.append(FBXMesh()); - - try { // call parseOBJGroup as long as it's returning true. Each successful call will // add a new meshPart to the geometry's single mesh. bool success = true; while (success) { - success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes); + success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes, scaleGuess); } FBXMesh &mesh = geometry.meshes[0]; + // if we got a hint about units, scale all the points + if (scaleGuess != 1.0f) { + for (int i = 0; i < mesh.vertices.size(); i++) { + mesh.vertices[i] *= scaleGuess; + } + } + mesh.meshExtents.reset(); foreach (const glm::vec3& vertex, mesh.vertices) { mesh.meshExtents.addPoint(vertex); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 83f473d8c5..9cf0d53244 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -70,6 +70,7 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& bool delayLoad, void* extra) { QSharedPointer resource = _resources.value(url); if (!resource.isNull()) { + removeUnusedResource(resource); return resource; } @@ -83,16 +84,14 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& return getResource(fallback, QUrl(), delayLoad); } - if (resource.isNull()) { - resource = createResource(url, fallback.isValid() ? - getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); - resource->setSelf(resource); - resource->setCache(this); - _resources.insert(url, resource); - - } else { - removeUnusedResource(resource); - } + resource = createResource(url, fallback.isValid() ? + getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); + resource->setSelf(resource); + resource->setCache(this); + _resources.insert(url, resource); + removeUnusedResource(resource); + resource->ensureLoading(); + return resource; } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index c74f714e8d..98843cc87f 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1775,10 +1775,10 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons return getResource(url, fallback, delayLoad, NULL).staticCast(); } -QSharedPointer GeometryCache::createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra) { +QSharedPointer GeometryCache::createResource(const QUrl& url, const QSharedPointer& fallback, + bool delayLoad, const void* extra) { QSharedPointer geometry(new NetworkGeometry(url, fallback.staticCast(), delayLoad), - &Resource::allReferencesCleared); + &Resource::allReferencesCleared); geometry->setLODParent(geometry); return geometry.staticCast(); } diff --git a/libraries/shared/src/Extents.h b/libraries/shared/src/Extents.h index 66b33114f7..024d72013a 100644 --- a/libraries/shared/src/Extents.h +++ b/libraries/shared/src/Extents.h @@ -48,7 +48,7 @@ public: 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]); } + float largestDimension() const {glm::vec3 s = size(); return glm::max(s[0], s[1], s[2]); } /// \return new Extents which is original rotated around orign by rotation Extents getRotated(const glm::quat& rotation) const {