From 37deb2674aa63d9750168e56eb3e78dfa8a23c67 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 13 Dec 2017 17:18:05 +0100 Subject: [PATCH 01/31] Texture coordinates are now encoded in vec2 half float in FBXReader --- libraries/fbx/src/FBXReader_Mesh.cpp | 43 +++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index c64cbcc90d..8e0b33b548 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -37,6 +37,10 @@ #include +#include +#include + +using vec2h = glm::tvec2; class Vertex { public: @@ -575,8 +579,9 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { int normalsSize = fbxMesh.normals.size() * sizeof(glm::vec3); int tangentsSize = fbxMesh.tangents.size() * sizeof(glm::vec3); int colorsSize = fbxMesh.colors.size() * sizeof(glm::vec3); - int texCoordsSize = fbxMesh.texCoords.size() * sizeof(glm::vec2); - int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(glm::vec2); + // Texture coordinates are stored in 2 half floats + int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); + int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(vec2h); int clusterIndicesSize = fbxMesh.clusterIndices.size() * sizeof(uint8_t); if (fbxMesh.clusters.size() > UINT8_MAX) { @@ -600,8 +605,32 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { attribBuffer->setSubData(normalsOffset, normalsSize, (gpu::Byte*) fbxMesh.normals.constData()); attribBuffer->setSubData(tangentsOffset, tangentsSize, (gpu::Byte*) fbxMesh.tangents.constData()); attribBuffer->setSubData(colorsOffset, colorsSize, (gpu::Byte*) fbxMesh.colors.constData()); - attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (gpu::Byte*) fbxMesh.texCoords.constData()); - attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (gpu::Byte*) fbxMesh.texCoords1.constData()); + + if (texCoordsSize > 0) { + QVector texCoordData; + texCoordData.reserve(fbxMesh.texCoords.size()); + for (auto& texCoordVec2f : fbxMesh.texCoords) { + vec2h texCoordVec2h; + + texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); + texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); + texCoordData.push_back(texCoordVec2h); + } + attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (gpu::Byte*) texCoordData.constData()); + } + + if (texCoords1Size > 0) { + QVector texCoordData; + texCoordData.reserve(fbxMesh.texCoords1.size()); + for (auto& texCoordVec2f : fbxMesh.texCoords1) { + vec2h texCoordVec2h; + + texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); + texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); + texCoordData.push_back(texCoordVec2h); + } + attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (gpu::Byte*) texCoordData.constData()); + } if (fbxMesh.clusters.size() < UINT8_MAX) { // yay! we can fit the clusterIndices within 8-bits @@ -636,16 +665,16 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (texCoordsSize) { mesh->addAttribute(gpu::Stream::TEXCOORD, model::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV))); + gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } if (texCoords1Size) { mesh->addAttribute( gpu::Stream::TEXCOORD1, model::BufferView(attribBuffer, texCoords1Offset, texCoords1Size, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV))); + gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } else if (texCoordsSize) { mesh->addAttribute(gpu::Stream::TEXCOORD1, model::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV))); + gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } if (clusterIndicesSize) { From 5ad69afa8a86f02926c12da4aebb3004e9bc0d22 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 14 Dec 2017 14:18:56 +0100 Subject: [PATCH 02/31] Added support for INT_2_10_10_10_REV format --- libraries/gpu-gl/src/gpu/gl/GLShared.h | 3 ++- libraries/gpu/src/gpu/Format.cpp | 1 + libraries/gpu/src/gpu/Format.h | 4 ++++ libraries/shared/src/GeometryUtil.cpp | 2 -- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl/src/gpu/gl/GLShared.h index d3ad7c028b..6c2948a736 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.h +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.h @@ -110,7 +110,8 @@ static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = { GL_UNSIGNED_SHORT, GL_BYTE, GL_UNSIGNED_BYTE, - GL_UNSIGNED_BYTE + GL_UNSIGNED_BYTE, + GL_INT_2_10_10_10_REV, }; bool checkGLError(const char* name = nullptr); diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index 3b153097cf..7614238a74 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -38,6 +38,7 @@ const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; const Element Element::VEC3F_XYZ{ VEC3, FLOAT, XYZ }; const Element Element::VEC4F_XYZW{ VEC4, FLOAT, XYZW }; +const Element Element::VEC4F_W2XYZ10{ VEC4, NINT2_10_10_10, XYZW }; const Element Element::INDEX_UINT16 { SCALAR, UINT16, INDEX }; const Element Element::INDEX_INT32 { SCALAR, INT32, INDEX }; const Element Element::PART_DRAWCALL{ VEC4, UINT32, PART }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 9d5d2fc49d..979b67f728 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -39,6 +39,7 @@ enum Type : uint8_t { NINT8, NUINT8, NUINT2, + NINT2_10_10_10, COMPRESSED, @@ -65,6 +66,7 @@ static const int TYPE_SIZE[NUM_TYPES] = { 2, 1, 1, + 4, 1 }; @@ -86,6 +88,7 @@ static const bool TYPE_IS_INTEGER[NUM_TYPES] = { false, false, false, + false, false, }; @@ -326,6 +329,7 @@ public: static const Element VEC2F_XY; static const Element VEC3F_XYZ; static const Element VEC4F_XYZW; + static const Element VEC4F_W2XYZ10; static const Element INDEX_UINT16; static const Element INDEX_INT32; static const Element PART_DRAWCALL; diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index e502d44a08..2ee761a1f7 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -338,8 +338,6 @@ int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle int clippedTriangleCount = 0; int i; - assert(clippedTriangleCount > 0); - for (i = 0; i < 3; i++) { pointDistanceToPlane[i] = plane.distance(triangleVertices[i]); arePointsClipped.set(i, pointDistanceToPlane[i] < 0.0f); From deb0b6b06f45652368218d4faf0b43d8575a6861 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 14 Dec 2017 16:12:44 +0100 Subject: [PATCH 03/31] Working interleaved normals and tangents. I'm still wondering why it works with blendshapes though... --- libraries/fbx/src/FBXReader.cpp | 55 +++++++++------- libraries/fbx/src/FBXReader_Mesh.cpp | 87 ++++++++++++++++---------- libraries/model/src/model/Geometry.cpp | 6 +- 3 files changed, 89 insertions(+), 59 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index e4fea00a34..041ce4ec70 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -303,8 +303,7 @@ FBXBlendshape extractBlendshape(const FBXNode& object) { return blendshape; } - -void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { +static void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { const glm::vec3& normal = mesh.normals.at(firstIndex); glm::vec3 bitangent = glm::cross(normal, mesh.vertices.at(secondIndex) - mesh.vertices.at(firstIndex)); if (glm::length(bitangent) < EPSILON) { @@ -316,6 +315,35 @@ void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { glm::normalize(bitangent), normalizedNormal); } +static void createTangents(FBXMesh& mesh, bool generateFromTexCoords) { + mesh.tangents.resize(mesh.vertices.size()); + + // if we have a normal map (and texture coordinates), we must compute tangents + if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { + foreach(const FBXMeshPart& part, mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + setTangents(mesh, part.quadIndices.at(i), part.quadIndices.at(i + 1)); + setTangents(mesh, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2)); + setTangents(mesh, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3)); + setTangents(mesh, part.quadIndices.at(i + 3), part.quadIndices.at(i)); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + setTangents(mesh, part.triangleIndices.at(i), part.triangleIndices.at(i + 1)); + setTangents(mesh, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2)); + setTangents(mesh, part.triangleIndices.at(i + 2), part.triangleIndices.at(i)); + } + if ((part.triangleIndices.size() % 3) != 0) { + qCDebug(modelformat) << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three "; + } + } + } else { + // Fill with a dummy value to force tangents to be present if there are normals + std::fill(mesh.tangents.begin(), mesh.tangents.end(), Vectors::UNIT_NEG_X); + } +} + QVector getIndices(const QVector ids, QVector modelIDs) { QVector indices; foreach (const QString& id, ids) { @@ -1570,27 +1598,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } - // if we have a normal map (and texture coordinates), we must compute tangents - if (generateTangents && !extracted.mesh.texCoords.isEmpty()) { - extracted.mesh.tangents.resize(extracted.mesh.vertices.size()); - foreach (const FBXMeshPart& part, extracted.mesh.parts) { - for (int i = 0; i < part.quadIndices.size(); i += 4) { - setTangents(extracted.mesh, part.quadIndices.at(i), part.quadIndices.at(i + 1)); - setTangents(extracted.mesh, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2)); - setTangents(extracted.mesh, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3)); - setTangents(extracted.mesh, part.quadIndices.at(i + 3), part.quadIndices.at(i)); - } - // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 - // This is most likely evidence of a further problem in extractMesh() - for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { - setTangents(extracted.mesh, part.triangleIndices.at(i), part.triangleIndices.at(i + 1)); - setTangents(extracted.mesh, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2)); - setTangents(extracted.mesh, part.triangleIndices.at(i + 2), part.triangleIndices.at(i)); - } - if ((part.triangleIndices.size() % 3) != 0){ - qCDebug(modelformat) << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three "; - } - } + if (!extracted.mesh.normals.empty()) { + createTangents(extracted.mesh, generateTangents); } // find the clusters with which the mesh is associated diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 8e0b33b548..ea27934b28 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -42,6 +42,10 @@ using vec2h = glm::tvec2; +using NormalType = glm::vec3; + +#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ + class Vertex { public: int originalIndex; @@ -576,35 +580,52 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { mesh->setVertexBuffer(vbv); // evaluate all attribute channels sizes - int normalsSize = fbxMesh.normals.size() * sizeof(glm::vec3); - int tangentsSize = fbxMesh.tangents.size() * sizeof(glm::vec3); - int colorsSize = fbxMesh.colors.size() * sizeof(glm::vec3); + const int normalsSize = fbxMesh.normals.size() * sizeof(NormalType); + const int tangentsSize = fbxMesh.tangents.size() * sizeof(NormalType); + // If there are normals then there should be tangents + assert(normalsSize == tangentsSize); + const auto normalsAndTangentsSize = normalsSize + tangentsSize; + const int normalsAndTangentsStride = 2 * sizeof(NormalType); + const int colorsSize = fbxMesh.colors.size() * sizeof(glm::vec3); // Texture coordinates are stored in 2 half floats - int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); - int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(vec2h); + const int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); + const int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(vec2h); int clusterIndicesSize = fbxMesh.clusterIndices.size() * sizeof(uint8_t); if (fbxMesh.clusters.size() > UINT8_MAX) { // we need 16 bits instead of just 8 for clusterIndices clusterIndicesSize *= 2; } - int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint8_t); + const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint8_t); - int normalsOffset = 0; - int tangentsOffset = normalsOffset + normalsSize; - int colorsOffset = tangentsOffset + tangentsSize; - int texCoordsOffset = colorsOffset + colorsSize; - int texCoords1Offset = texCoordsOffset + texCoordsSize; - int clusterIndicesOffset = texCoords1Offset + texCoords1Size; - int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; - int totalAttributeSize = clusterWeightsOffset + clusterWeightsSize; + // Normals and tangents are interleaved + const int normalsOffset = 0; + const int tangentsOffset = normalsOffset + sizeof(NormalType); + const int colorsOffset = normalsOffset + normalsSize + tangentsSize; + const int texCoordsOffset = colorsOffset + colorsSize; + const int texCoords1Offset = texCoordsOffset + texCoordsSize; + const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; + const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; + const int totalAttributeSize = clusterWeightsOffset + clusterWeightsSize; // Copy all attribute data in a single attribute buffer auto attribBuffer = std::make_shared(); attribBuffer->resize(totalAttributeSize); - attribBuffer->setSubData(normalsOffset, normalsSize, (gpu::Byte*) fbxMesh.normals.constData()); - attribBuffer->setSubData(tangentsOffset, tangentsSize, (gpu::Byte*) fbxMesh.tangents.constData()); - attribBuffer->setSubData(colorsOffset, colorsSize, (gpu::Byte*) fbxMesh.colors.constData()); + + // Interleave normals and tangents + { + QVector normalsAndTangents; + + normalsAndTangents.reserve(fbxMesh.normals.size() + fbxMesh.tangents.size()); + for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); + normalIt != fbxMesh.normals.constEnd(); + ++normalIt, ++tangentIt) { + normalsAndTangents.push_back(*normalIt); + normalsAndTangents.push_back(*tangentIt); + } + attribBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.constData()); + } + attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); if (texCoordsSize > 0) { QVector texCoordData; @@ -616,7 +637,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); texCoordData.push_back(texCoordVec2h); } - attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (gpu::Byte*) texCoordData.constData()); + attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); } if (texCoords1Size > 0) { @@ -629,7 +650,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); texCoordData.push_back(texCoordVec2h); } - attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (gpu::Byte*) texCoordData.constData()); + attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); } if (fbxMesh.clusters.size() < UINT8_MAX) { @@ -641,31 +662,29 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); } - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (gpu::Byte*) clusterIndices.constData()); + attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); } else { - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (gpu::Byte*) fbxMesh.clusterIndices.constData()); + attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } - attribBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (gpu::Byte*) fbxMesh.clusterWeights.constData()); + attribBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); if (normalsSize) { mesh->addAttribute(gpu::Stream::NORMAL, - model::BufferView(attribBuffer, normalsOffset, normalsSize, - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ))); - } - if (tangentsSize) { + model::BufferView(attribBuffer, normalsOffset, normalsAndTangentsSize, + normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); mesh->addAttribute(gpu::Stream::TANGENT, - model::BufferView(attribBuffer, tangentsOffset, tangentsSize, - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ))); + model::BufferView(attribBuffer, tangentsOffset, normalsAndTangentsSize, + normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); } if (colorsSize) { mesh->addAttribute(gpu::Stream::COLOR, - model::BufferView(attribBuffer, colorsOffset, colorsSize, - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB))); + model::BufferView(attribBuffer, colorsOffset, colorsSize, + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB))); } if (texCoordsSize) { mesh->addAttribute(gpu::Stream::TEXCOORD, - model::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + model::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, + gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } if (texCoords1Size) { mesh->addAttribute( gpu::Stream::TEXCOORD1, @@ -673,8 +692,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } else if (texCoordsSize) { mesh->addAttribute(gpu::Stream::TEXCOORD1, - model::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + model::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, + gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } if (clusterIndicesSize) { diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index 5627746c43..f6c17adf17 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -72,12 +72,14 @@ void Mesh::evalVertexStream() { int channelNum = 0; if (hasVertexData()) { - _vertexStream.addBuffer(_vertexBuffer._buffer, _vertexBuffer._offset, _vertexFormat->getChannelStride(channelNum)); + auto stride = glm::max(_vertexFormat->getChannelStride(channelNum), _vertexBuffer._stride); + _vertexStream.addBuffer(_vertexBuffer._buffer, _vertexBuffer._offset, stride); channelNum++; } for (auto attrib : _attributeBuffers) { BufferView& view = attrib.second; - _vertexStream.addBuffer(view._buffer, view._offset, _vertexFormat->getChannelStride(channelNum)); + auto stride = glm::max(_vertexFormat->getChannelStride(channelNum), view._stride); + _vertexStream.addBuffer(view._buffer, view._offset, stride); channelNum++; } } From f38e473218b4e21d60ed6b6f396150611ef2bbba Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 14 Dec 2017 17:57:34 +0100 Subject: [PATCH 04/31] Working packing of normals and tangents in GL_INT_10_10_10_2_REV format. Need to check this with all available 3D data input formats --- libraries/fbx/src/FBXReader.cpp | 11 ++---- libraries/fbx/src/FBXReader.h | 11 ++++++ libraries/fbx/src/FBXReader_Mesh.cpp | 31 ++++++++++++--- libraries/gpu/src/gpu/Format.cpp | 2 +- libraries/gpu/src/gpu/Format.h | 2 +- .../render-utils/src/MeshPartPayload.cpp | 2 +- libraries/render-utils/src/Model.cpp | 39 ++++++++++++++++--- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 041ce4ec70..a5d15350d5 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -316,10 +316,10 @@ static void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { } static void createTangents(FBXMesh& mesh, bool generateFromTexCoords) { - mesh.tangents.resize(mesh.vertices.size()); - // if we have a normal map (and texture coordinates), we must compute tangents if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { + mesh.tangents.resize(mesh.vertices.size()); + foreach(const FBXMeshPart& part, mesh.parts) { for (int i = 0; i < part.quadIndices.size(); i += 4) { setTangents(mesh, part.quadIndices.at(i), part.quadIndices.at(i + 1)); @@ -338,9 +338,6 @@ static void createTangents(FBXMesh& mesh, bool generateFromTexCoords) { qCDebug(modelformat) << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three "; } } - } else { - // Fill with a dummy value to force tangents to be present if there are normals - std::fill(mesh.tangents.begin(), mesh.tangents.end(), Vectors::UNIT_NEG_X); } } @@ -1598,9 +1595,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } - if (!extracted.mesh.normals.empty()) { - createTangents(extracted.mesh, generateTangents); - } + createTangents(extracted.mesh, generateTangents); // find the clusters with which the mesh is associated QVector clusterIDs; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 843a874a62..34d63b098a 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -33,6 +33,15 @@ class QIODevice; class FBXNode; +#define FBX_PACK_NORMALS 1 + +#if FBX_PACK_NORMALS +using NormalType = glm::uint32; +#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2 +#else +using NormalType = glm::vec3; +#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ +#endif /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing @@ -114,6 +123,8 @@ public: QHash meshes; static void buildModelMesh(FBXMesh& extractedMesh, const QString& url); + static glm::vec3 normalizeDirForPacking(const glm::vec3& dir); + FBXTexture getTexture(const QString& textureID); QHash _textureNames; diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index ea27934b28..e684332738 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -42,10 +42,6 @@ using vec2h = glm::tvec2; -using NormalType = glm::vec3; - -#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ - class Vertex { public: int originalIndex; @@ -551,6 +547,14 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn return data.extracted; } +glm::vec3 FBXReader::normalizeDirForPacking(const glm::vec3& dir) { + auto maxCoord = glm::max(fabsf(dir.x), glm::max(fabsf(dir.y), fabsf(dir.z))); + if (maxCoord > 1e-6f) { + return dir / maxCoord; + } + return dir; +} + void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*"); @@ -579,6 +583,12 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { gpu::BufferView vbv(vb, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); mesh->setVertexBuffer(vbv); + if (!fbxMesh.normals.empty() && fbxMesh.tangents.empty()) { + // Fill with a dummy value to force tangents to be present if there are normals + fbxMesh.tangents.reserve(fbxMesh.normals.size()); + std::fill_n(std::back_inserter(fbxMesh.tangents), fbxMesh.normals.size(), Vectors::UNIT_X); + } + // evaluate all attribute channels sizes const int normalsSize = fbxMesh.normals.size() * sizeof(NormalType); const int tangentsSize = fbxMesh.tangents.size() * sizeof(NormalType); @@ -620,8 +630,17 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); normalIt != fbxMesh.normals.constEnd(); ++normalIt, ++tangentIt) { - normalsAndTangents.push_back(*normalIt); - normalsAndTangents.push_back(*tangentIt); +#if FBX_PACK_NORMALS + const auto normal = normalizeDirForPacking(*normalIt); + const auto tangent = normalizeDirForPacking(*tangentIt); + const auto packedNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); + const auto packedTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); +#else + const auto packedNormal = *normalIt; + const auto packedTangent = *tangentIt; +#endif + normalsAndTangents.push_back(packedNormal); + normalsAndTangents.push_back(packedTangent); } attribBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.constData()); } diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index 7614238a74..c4b6b39723 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -38,7 +38,7 @@ const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; const Element Element::VEC3F_XYZ{ VEC3, FLOAT, XYZ }; const Element Element::VEC4F_XYZW{ VEC4, FLOAT, XYZW }; -const Element Element::VEC4F_W2XYZ10{ VEC4, NINT2_10_10_10, XYZW }; +const Element Element::VEC4F_NORMALIZED_XYZ10W2{ VEC4, NINT2_10_10_10, XYZW }; const Element Element::INDEX_UINT16 { SCALAR, UINT16, INDEX }; const Element Element::INDEX_INT32 { SCALAR, INT32, INDEX }; const Element Element::PART_DRAWCALL{ VEC4, UINT32, PART }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 979b67f728..17102d0415 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -329,7 +329,7 @@ public: static const Element VEC2F_XY; static const Element VEC3F_XYZ; static const Element VEC4F_XYZW; - static const Element VEC4F_W2XYZ10; + static const Element VEC4F_NORMALIZED_XYZ10W2; static const Element INDEX_UINT16; static const Element INDEX_INT32; static const Element PART_DRAWCALL; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 1ea3e1a705..96451fa23d 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -518,7 +518,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { ModelPointer model = _model.lock(); if (model) { batch.setInputBuffer(0, model->_blendedVertexBuffers[_meshIndex], 0, sizeof(glm::vec3)); - batch.setInputBuffer(1, model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3)); + batch.setInputBuffer(1, model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(NormalType)); batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2)); } else { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c4bc435691..d918970af5 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -26,6 +26,8 @@ #include #include +#include + #include "AbstractViewStateInterface.h" #include "MeshPartPayload.h" @@ -315,10 +317,21 @@ bool Model::updateGeometry() { // TODO? make _blendedVertexBuffers a map instead of vector and only add for meshes with blendshapes? auto buffer = std::make_shared(); if (!mesh.blendshapes.isEmpty()) { - buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); - buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); + buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + mesh.normals.size() * sizeof(NormalType)); + buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) mesh.vertices.constData()); +#if FBX_PACK_NORMALS + std::vector packedNormals; + packedNormals.reserve(mesh.normals.size()); + for (auto normal : mesh.normals) { + normal = FBXReader::normalizeDirForPacking(normal); + packedNormals.push_back(glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f))); + } + const auto normalsData = packedNormals.data(); +#else + const auto normalsData = mesh.normals.constData(); +#endif buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); + mesh.normals.size() * sizeof(NormalType), (const gpu::Byte*) normalsData); } _blendedVertexBuffers.push_back(buffer); } @@ -1183,6 +1196,9 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo _appliedBlendNumber = blendNumber; const FBXGeometry& fbxGeometry = getFBXGeometry(); int index = 0; +#if FBX_PACK_NORMALS + std::vector packedNormals; +#endif for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); if (mesh.blendshapes.isEmpty()) { @@ -1190,9 +1206,20 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo } gpu::BufferPointer& buffer = _blendedVertexBuffers[i]; - buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) vertices.constData() + index*sizeof(glm::vec3)); - buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) normals.constData() + index*sizeof(glm::vec3)); + const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); + buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index*sizeof(glm::vec3)); +#if FBX_PACK_NORMALS + packedNormals.clear(); + packedNormals.reserve(normals.size()); + for (auto normal : normals) { + normal = FBXReader::normalizeDirForPacking(normal); + packedNormals.push_back(glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f))); + } + const auto normalsData = packedNormals.data(); +#else + const auto normalsData = mesh.normals.constData(); +#endif + buffer->setSubData(verticesSize, normals.size() * sizeof(NormalType), (const gpu::Byte*) normalsData + index*sizeof(NormalType)); index += mesh.vertices.size(); } From 2a325d45e0ebb64a046c95371f1357b77d060464 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 15 Dec 2017 09:21:36 +0100 Subject: [PATCH 05/31] Mesh color attributes are now stored as RGBA8 --- libraries/fbx/src/FBXReader_Mesh.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index e684332738..2f307f0afa 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -41,6 +41,7 @@ #include using vec2h = glm::tvec2; +using ColorType = glm::uint32; class Vertex { public: @@ -596,7 +597,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { assert(normalsSize == tangentsSize); const auto normalsAndTangentsSize = normalsSize + tangentsSize; const int normalsAndTangentsStride = 2 * sizeof(NormalType); - const int colorsSize = fbxMesh.colors.size() * sizeof(glm::vec3); + const int colorsSize = fbxMesh.colors.size() * sizeof(ColorType); // Texture coordinates are stored in 2 half floats const int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); const int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(vec2h); @@ -623,8 +624,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { attribBuffer->resize(totalAttributeSize); // Interleave normals and tangents - { - QVector normalsAndTangents; + if (normalsSize > 0) { + std::vector normalsAndTangents; normalsAndTangents.reserve(fbxMesh.normals.size() + fbxMesh.tangents.size()); for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); @@ -642,9 +643,18 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { normalsAndTangents.push_back(packedNormal); normalsAndTangents.push_back(packedTangent); } - attribBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.constData()); + attribBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); + } + + if (colorsSize > 0) { + std::vector colors; + + colors.reserve(fbxMesh.colors.size()); + for (const auto& color : fbxMesh.colors) { + colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); + } + attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); } - attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); if (texCoordsSize > 0) { QVector texCoordData; @@ -698,7 +708,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (colorsSize) { mesh->addAttribute(gpu::Stream::COLOR, model::BufferView(attribBuffer, colorsOffset, colorsSize, - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB))); + gpu::Element::COLOR_RGBA_32)); } if (texCoordsSize) { mesh->addAttribute(gpu::Stream::TEXCOORD, From 264f41472d1eb3db04858f03b6f81bd484175596 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 15 Dec 2017 12:06:07 +0100 Subject: [PATCH 06/31] Added tangents to blendshape for possible break of bump mapping when doing blend shape animations --- libraries/fbx/src/FBX.h | 1 + libraries/fbx/src/FBXReader.cpp | 96 +++++++++++++++---- libraries/fbx/src/FBXReader_Mesh.cpp | 8 ++ .../render-utils/src/MeshPartPayload.cpp | 3 +- libraries/render-utils/src/Model.cpp | 96 ++++++++++++------- libraries/render-utils/src/Model.h | 4 +- 6 files changed, 151 insertions(+), 57 deletions(-) diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 7d3328a2dd..2150463065 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -60,6 +60,7 @@ public: QVector indices; QVector vertices; QVector normals; + QVector tangents; }; struct FBXJointShapeInfo { diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index a5d15350d5..1694e31e1d 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -303,36 +303,45 @@ FBXBlendshape extractBlendshape(const FBXNode& object) { return blendshape; } -static void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { - const glm::vec3& normal = mesh.normals.at(firstIndex); - glm::vec3 bitangent = glm::cross(normal, mesh.vertices.at(secondIndex) - mesh.vertices.at(firstIndex)); - if (glm::length(bitangent) < EPSILON) { - return; +using IndexAccessor = std::function; + +static void setTangents(const FBXMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, + const QVector& vertices, const QVector& normals, QVector& tangents) { + glm::vec3 vertex[2]; + glm::vec3 normal; + glm::vec3* tangent = vertexAccessor(mesh, firstIndex, secondIndex, vertex, normal); + if (tangent) { + glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); + if (glm::length(bitangent) < EPSILON) { + return; + } + glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex); + glm::vec3 normalizedNormal = glm::normalize(normal); + *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * + glm::normalize(bitangent), normalizedNormal); } - glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex); - glm::vec3 normalizedNormal = glm::normalize(normal); - mesh.tangents[firstIndex] += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * - glm::normalize(bitangent), normalizedNormal); } -static void createTangents(FBXMesh& mesh, bool generateFromTexCoords) { +static void createTangents(const FBXMesh& mesh, bool generateFromTexCoords, + const QVector& vertices, const QVector& normals, QVector& tangents, + IndexAccessor accessor) { // if we have a normal map (and texture coordinates), we must compute tangents if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { - mesh.tangents.resize(mesh.vertices.size()); + tangents.resize(vertices.size()); foreach(const FBXMeshPart& part, mesh.parts) { for (int i = 0; i < part.quadIndices.size(); i += 4) { - setTangents(mesh, part.quadIndices.at(i), part.quadIndices.at(i + 1)); - setTangents(mesh, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2)); - setTangents(mesh, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3)); - setTangents(mesh, part.quadIndices.at(i + 3), part.quadIndices.at(i)); + setTangents(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1), vertices, normals, tangents); + setTangents(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2), vertices, normals, tangents); + setTangents(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3), vertices, normals, tangents); + setTangents(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i), vertices, normals, tangents); } // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 // This is most likely evidence of a further problem in extractMesh() for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { - setTangents(mesh, part.triangleIndices.at(i), part.triangleIndices.at(i + 1)); - setTangents(mesh, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2)); - setTangents(mesh, part.triangleIndices.at(i + 2), part.triangleIndices.at(i)); + setTangents(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1), vertices, normals, tangents); + setTangents(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2), vertices, normals, tangents); + setTangents(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i), vertices, normals, tangents); } if ((part.triangleIndices.size() % 3) != 0) { qCDebug(modelformat) << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three "; @@ -341,6 +350,52 @@ static void createTangents(FBXMesh& mesh, bool generateFromTexCoords) { } } +static void createMeshTangents(FBXMesh& mesh, bool generateFromTexCoords) { + // This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't + // const in the lambda function. + auto& tangents = mesh.tangents; + createTangents(mesh, generateFromTexCoords, mesh.vertices, mesh.normals, mesh.tangents, + [&](const FBXMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { + outVertices[0] = mesh.vertices[firstIndex]; + outVertices[1] = mesh.vertices[secondIndex]; + outNormal = mesh.normals[firstIndex]; + return &(tangents[firstIndex]); + }); +} + +static void createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape) { + // Create lookup to get index in blend shape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + + for (int indexInBlendShape = 0; indexInBlendShape < blendShape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendShape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + createTangents(mesh, generateFromTexCoords, blendShape.vertices, blendShape.normals, blendShape.tangents, + [&](const FBXMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { + const auto index1 = reverseIndices[firstIndex]; + const auto index2 = reverseIndices[secondIndex]; + + if (index1 < blendShape.vertices.size()) { + outVertices[0] = blendShape.vertices[index1]; + if (index2 < blendShape.vertices.size()) { + outVertices[1] = blendShape.vertices[index2]; + } else { + // Index isn't in the blend shape so return vertex from mesh + outVertices[1] = mesh.vertices[secondIndex]; + } + outNormal = blendShape.normals[index1]; + return &blendShape.tangents[index1]; + } else { + // Index isn't in blend shape so return nullptr + return (glm::vec3*)nullptr; + } + }); +} + QVector getIndices(const QVector ids, QVector modelIDs) { QVector indices; foreach (const QString& id, ids) { @@ -1595,7 +1650,10 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } - createTangents(extracted.mesh, generateTangents); + createMeshTangents(extracted.mesh, generateTangents); + for (auto& blendShape : extracted.mesh.blendshapes) { + createBlendShapeTangents(extracted.mesh, generateTangents, blendShape); + } // find the clusters with which the mesh is associated QVector clusterIDs; diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 2f307f0afa..fbe74ccf99 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -589,6 +589,14 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { fbxMesh.tangents.reserve(fbxMesh.normals.size()); std::fill_n(std::back_inserter(fbxMesh.tangents), fbxMesh.normals.size(), Vectors::UNIT_X); } + // Same thing with blend shapes + for (auto& blendShape : fbxMesh.blendshapes) { + if (!blendShape.normals.empty() && blendShape.tangents.empty()) { + // Fill with a dummy value to force tangents to be present if there are normals + blendShape.tangents.reserve(blendShape.normals.size()); + std::fill_n(std::back_inserter(fbxMesh.tangents), blendShape.normals.size(), Vectors::UNIT_X); + } + } // evaluate all attribute channels sizes const int normalsSize = fbxMesh.normals.size() * sizeof(NormalType); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 96451fa23d..c3534da114 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -518,7 +518,8 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { ModelPointer model = _model.lock(); if (model) { batch.setInputBuffer(0, model->_blendedVertexBuffers[_meshIndex], 0, sizeof(glm::vec3)); - batch.setInputBuffer(1, model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(NormalType)); + // Stride is 2*sizeof(glm::vec3) because normal and tangents are interleaved + batch.setInputBuffer(1, model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), 2*sizeof(NormalType)); batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2)); } else { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d918970af5..e341fb886e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -317,21 +317,29 @@ bool Model::updateGeometry() { // TODO? make _blendedVertexBuffers a map instead of vector and only add for meshes with blendshapes? auto buffer = std::make_shared(); if (!mesh.blendshapes.isEmpty()) { - buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + mesh.normals.size() * sizeof(NormalType)); - buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) mesh.vertices.constData()); + std::vector normalsAndTangents; + normalsAndTangents.reserve(mesh.normals.size() + mesh.tangents.size()); + + for (auto normalIt = mesh.normals.begin(), tangentIt = mesh.tangents.begin(); + normalIt != mesh.normals.end(); + ++normalIt, ++tangentIt) { #if FBX_PACK_NORMALS - std::vector packedNormals; - packedNormals.reserve(mesh.normals.size()); - for (auto normal : mesh.normals) { - normal = FBXReader::normalizeDirForPacking(normal); - packedNormals.push_back(glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f))); - } - const auto normalsData = packedNormals.data(); + const auto normal = FBXReader::normalizeDirForPacking(*normalIt); + const auto tangent = FBXReader::normalizeDirForPacking(*tangentIt); + const auto finalNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); + const auto finalTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); #else - const auto normalsData = mesh.normals.constData(); + const auto finalNormal = *normalIt; + const auto finalTangent = *tangentIt; #endif + normalsAndTangents.push_back(finalNormal); + normalsAndTangents.push_back(finalTangent); + } + + buffer->resize(mesh.vertices.size() * (sizeof(glm::vec3) + 2 * sizeof(NormalType))); + buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) mesh.vertices.constData()); buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * sizeof(NormalType), (const gpu::Byte*) normalsData); + mesh.normals.size() * 2 * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); } _blendedVertexBuffers.push_back(buffer); } @@ -974,7 +982,7 @@ Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointe void Blender::run() { DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); - QVector vertices, normals; + QVector vertices, normals, tangents; if (_model) { int offset = 0; foreach (const FBXMesh& mesh, _meshes) { @@ -983,8 +991,10 @@ void Blender::run() { } vertices += mesh.vertices; normals += mesh.normals; + tangents += mesh.tangents; glm::vec3* meshVertices = vertices.data() + offset; glm::vec3* meshNormals = normals.data() + offset; + glm::vec3* meshTangents = tangents.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++) { @@ -999,6 +1009,7 @@ void Blender::run() { int index = blendshape.indices.at(j); meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; + meshTangents[index] += blendshape.tangents.at(j) * normalCoefficient; } } } @@ -1007,7 +1018,7 @@ void Blender::run() { QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector&, vertices), - Q_ARG(const QVector&, normals)); + Q_ARG(const QVector&, normals), Q_ARG(const QVector&, tangents)); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { @@ -1188,7 +1199,7 @@ bool Model::maybeStartBlender() { } void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals) { + const QVector& vertices, const QVector& normals, const QVector& tangents) { auto geometryRef = geometry.lock(); if (!geometryRef || _renderGeometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) { return; @@ -1196,9 +1207,7 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo _appliedBlendNumber = blendNumber; const FBXGeometry& fbxGeometry = getFBXGeometry(); int index = 0; -#if FBX_PACK_NORMALS - std::vector packedNormals; -#endif + std::vector normalsAndTangents; for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); if (mesh.blendshapes.isEmpty()) { @@ -1206,22 +1215,38 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo } gpu::BufferPointer& buffer = _blendedVertexBuffers[i]; - const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); - buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index*sizeof(glm::vec3)); -#if FBX_PACK_NORMALS - packedNormals.clear(); - packedNormals.reserve(normals.size()); - for (auto normal : normals) { - normal = FBXReader::normalizeDirForPacking(normal); - packedNormals.push_back(glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f))); - } - const auto normalsData = packedNormals.data(); -#else - const auto normalsData = mesh.normals.constData(); -#endif - buffer->setSubData(verticesSize, normals.size() * sizeof(NormalType), (const gpu::Byte*) normalsData + index*sizeof(NormalType)); + const auto vertexCount = mesh.vertices.size(); + const auto verticesSize = vertexCount * sizeof(glm::vec3); + const auto offset = index * sizeof(glm::vec3); - index += mesh.vertices.size(); + normalsAndTangents.clear(); + normalsAndTangents.reserve(normals.size()+tangents.size()); + + // Interleave normals and tangents + auto normalsRange = std::make_pair(normals.begin() + index, normals.begin() + index + vertexCount); + auto tangentsRange = std::make_pair(tangents.begin() + index, tangents.begin() + index + vertexCount); + + for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; + normalIt != normalsRange.second; + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + const auto normal = FBXReader::normalizeDirForPacking(*normalIt); + const auto tangent = FBXReader::normalizeDirForPacking(*tangentIt); + const auto finalNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); + const auto finalTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); +#else + const auto finalNormal = *normalIt; + const auto finalTangent = *tangentIt; +#endif + normalsAndTangents.push_back(finalNormal); + normalsAndTangents.push_back(finalTangent); + } + assert(normalsAndTangents.size() == 2 * vertexCount); + + buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + offset); + buffer->setSubData(verticesSize, 2 * vertexCount * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + + index += vertexCount; } } @@ -1388,10 +1413,11 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) { } } -void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, - const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals) { +void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, + const QVector& vertices, const QVector& normals, + const QVector& tangents) { if (model) { - model->setBlendedVertices(blendNumber, geometry, vertices, normals); + model->setBlendedVertices(blendNumber, geometry, vertices, normals, tangents); } _pendingBlenders--; { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 7568a17342..7019ac9b27 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -114,7 +114,7 @@ public: /// Sets blended vertices computed in a separate thread. void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals); + const QVector& vertices, const QVector& normals, const QVector& tangents); bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } bool isAddedToScene() const { return _addedToScene; } @@ -440,7 +440,7 @@ public: public slots: void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals); + const QVector& vertices, const QVector& normals, const QVector& tangents); private: using Mutex = std::mutex; From 116756c9762621c99e5206c3511d37ee26937e12 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 18 Dec 2017 11:22:43 +0100 Subject: [PATCH 07/31] Parallel optimizations of setBlendedVertices --- libraries/render-utils/src/Model.cpp | 82 ++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index e341fb886e..4afaa9982d 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -24,6 +24,8 @@ #include #include #include +#include + #include #include @@ -292,6 +294,34 @@ void Model::reset() { } } +#if FBX_PACK_NORMALS +static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { + auto absNormal = glm::abs(normal); + auto absTangent = glm::abs(tangent); + normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); + tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); + normal = glm::clamp(normal, -1.0f, 1.0f); + tangent = glm::clamp(tangent, -1.0f, 1.0f); + normal *= 511.0f; + tangent *= 511.0f; + normal = glm::round(normal); + tangent = glm::round(tangent); + + glm::detail::i10i10i10i2 normalStruct; + glm::detail::i10i10i10i2 tangentStruct; + normalStruct.data.x = int(normal.x); + normalStruct.data.y = int(normal.y); + normalStruct.data.z = int(normal.z); + normalStruct.data.w = 0; + tangentStruct.data.x = int(tangent.x); + tangentStruct.data.y = int(tangent.y); + tangentStruct.data.z = int(tangent.z); + tangentStruct.data.w = 0; + packedNormal = normalStruct.pack; + packedTangent = tangentStruct.pack; +} +#endif + bool Model::updateGeometry() { bool needFullUpdate = false; @@ -324,10 +354,9 @@ bool Model::updateGeometry() { normalIt != mesh.normals.end(); ++normalIt, ++tangentIt) { #if FBX_PACK_NORMALS - const auto normal = FBXReader::normalizeDirForPacking(*normalIt); - const auto tangent = FBXReader::normalizeDirForPacking(*tangentIt); - const auto finalNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); - const auto finalTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); + glm::uint32 finalNormal; + glm::uint32 finalTangent; + packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); #else const auto finalNormal = *normalIt; const auto finalTangent = *tangentIt; @@ -1220,28 +1249,57 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo const auto offset = index * sizeof(glm::vec3); normalsAndTangents.clear(); - normalsAndTangents.reserve(normals.size()+tangents.size()); + normalsAndTangents.resize(normals.size()+tangents.size()); + assert(normalsAndTangents.size() == 2 * vertexCount); // Interleave normals and tangents +#if 0 + // Sequential version for debugging auto normalsRange = std::make_pair(normals.begin() + index, normals.begin() + index + vertexCount); auto tangentsRange = std::make_pair(tangents.begin() + index, tangents.begin() + index + vertexCount); + auto normalsAndTangentsIt = normalsAndTangents.begin(); for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; normalIt != normalsRange.second; ++normalIt, ++tangentIt) { #if FBX_PACK_NORMALS - const auto normal = FBXReader::normalizeDirForPacking(*normalIt); - const auto tangent = FBXReader::normalizeDirForPacking(*tangentIt); - const auto finalNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); - const auto finalTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); + glm::uint32 finalNormal; + glm::uint32 finalTangent; + packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); #else const auto finalNormal = *normalIt; const auto finalTangent = *tangentIt; #endif - normalsAndTangents.push_back(finalNormal); - normalsAndTangents.push_back(finalTangent); + *normalsAndTangentsIt = finalNormal; + ++normalsAndTangentsIt; + *normalsAndTangentsIt = finalTangent; + ++normalsAndTangentsIt; } - assert(normalsAndTangents.size() == 2 * vertexCount); +#else + // Parallel version for performance + tbb::parallel_for(tbb::blocked_range(index, index+vertexCount), [&](const tbb::blocked_range& range) { + auto normalsRange = std::make_pair(normals.begin() + range.begin(), normals.begin() + range.end()); + auto tangentsRange = std::make_pair(tangents.begin() + range.begin(), tangents.begin() + range.end()); + auto normalsAndTangentsIt = normalsAndTangents.begin() + (range.begin()-index)*2; + + for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; + normalIt != normalsRange.second; + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); +#else + const auto finalNormal = *normalIt; + const auto finalTangent = *tangentIt; +#endif + *normalsAndTangentsIt = finalNormal; + ++normalsAndTangentsIt; + *normalsAndTangentsIt = finalTangent; + ++normalsAndTangentsIt; + } + }); +#endif buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + offset); buffer->setSubData(verticesSize, 2 * vertexCount * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); From 24cc19aeaa7af3e270c90445c8d6a243f6f72ca9 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 18 Dec 2017 14:37:50 +0100 Subject: [PATCH 08/31] Partial fix of colors per vertex of FBX models exported by Blender 2.79 --- libraries/fbx/src/FBXReader_Mesh.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index fbe74ccf99..b9549e2c4e 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -41,7 +41,16 @@ #include using vec2h = glm::tvec2; + +#define FBX_PACK_COLORS 1 + +#if FBX_PACK_COLORS using ColorType = glm::uint32; +#define FBX_COLOR_ELEMENT gpu::Element::COLOR_RGBA_32 +#else +using ColorType = glm::vec3; +#define FBX_COLOR_ELEMENT gpu::Element::VEC3F_XYZ +#endif class Vertex { public: @@ -230,7 +239,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn foreach (const FBXNode& subdata, child.children) { if (subdata.name == "Colors") { data.colors = createVec4VectorRGBA(getDoubleVector(subdata), data.averageColor); - } else if (subdata.name == "ColorsIndex") { + } else if (subdata.name == "ColorsIndex" || subdata.name == "ColorIndex") { data.colorIndices = getIntVector(subdata); } else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == BY_VERTICE) { @@ -655,6 +664,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } if (colorsSize > 0) { +#if FBX_PACK_COLORS std::vector colors; colors.reserve(fbxMesh.colors.size()); @@ -662,6 +672,9 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); } attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); +#else + attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); +#endif } if (texCoordsSize > 0) { @@ -715,8 +728,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } if (colorsSize) { mesh->addAttribute(gpu::Stream::COLOR, - model::BufferView(attribBuffer, colorsOffset, colorsSize, - gpu::Element::COLOR_RGBA_32)); + model::BufferView(attribBuffer, colorsOffset, colorsSize, FBX_COLOR_ELEMENT)); } if (texCoordsSize) { mesh->addAttribute(gpu::Stream::TEXCOORD, From 72ef716b13e8febbea1e1ddc4443684c86459558 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 21 Dec 2017 09:52:06 +0100 Subject: [PATCH 09/31] Fixed warnings on Mac and Ubuntu --- libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 528a2b524b..7b3db4e4fe 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -218,6 +218,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::NINT8: result = GL_RGBA8_SNORM; break; + case gpu::NINT2_10_10_10: case gpu::NUINT32: case gpu::NINT32: case gpu::COMPRESSED: @@ -502,6 +503,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::COMPRESSED: case gpu::NUINT2: + case gpu::NINT2_10_10_10: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -553,6 +555,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::COMPRESSED: case gpu::NUINT2: + case gpu::NINT2_10_10_10: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -671,6 +674,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; case gpu::NUINT32: case gpu::NINT32: + case gpu::NINT2_10_10_10: case gpu::COMPRESSED: case gpu::NUM_TYPES: // quiet compiler Q_UNREACHABLE(); From 752952507d9c03a13eb151e753471b72052bca00 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 22 Dec 2017 14:18:02 +0100 Subject: [PATCH 10/31] Fixed memory leak in ModelScriptingInterface::appendMeshes and wrong normal transformation in ModelScriptingInterface --- .../src/ModelScriptingInterface.cpp | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/libraries/script-engine/src/ModelScriptingInterface.cpp b/libraries/script-engine/src/ModelScriptingInterface.cpp index 3234a079ec..e58afeaba8 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.cpp +++ b/libraries/script-engine/src/ModelScriptingInterface.cpp @@ -60,20 +60,20 @@ QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { // alloc the resulting mesh gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); - unsigned char* combinedVertexData = new unsigned char[combinedVertexSize]; - unsigned char* combinedVertexDataCursor = combinedVertexData; + std::unique_ptr combinedVertexData{ new unsigned char[combinedVertexSize] }; + unsigned char* combinedVertexDataCursor = combinedVertexData.get(); gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); - unsigned char* combinedColorData = new unsigned char[combinedColorSize]; - unsigned char* combinedColorDataCursor = combinedColorData; + std::unique_ptr combinedColorData{ new unsigned char[combinedColorSize] }; + unsigned char* combinedColorDataCursor = combinedColorData.get(); gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); - unsigned char* combinedNormalData = new unsigned char[combinedNormalSize]; - unsigned char* combinedNormalDataCursor = combinedNormalData; + std::unique_ptr combinedNormalData{ new unsigned char[combinedNormalSize] }; + unsigned char* combinedNormalDataCursor = combinedNormalData.get(); gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); - unsigned char* combinedIndexData = new unsigned char[combinedIndexSize]; - unsigned char* combinedIndexDataCursor = combinedIndexData; + std::unique_ptr combinedIndexData{ new unsigned char[combinedIndexSize] }; + unsigned char* combinedIndexDataCursor = combinedIndexData.get(); uint32_t indexStartOffset { 0 }; @@ -105,27 +105,27 @@ QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { model::MeshPointer result(new model::Mesh()); gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData); + gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData.get()); gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); result->setVertexBuffer(combinedVertexBufferView); int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData); + gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData.get()); gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); result->addAttribute(attributeTypeColor, combinedColorsBufferView); int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData); + gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData.get()); gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); - gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData); + gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData.get()); gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); result->setIndexBuffer(combinedIndexesBufferView); @@ -152,9 +152,10 @@ QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshPro return QScriptValue(false); } + const auto inverseTransposeTransform = glm::inverse(glm::transpose(transform)); model::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, [&](glm::vec3 color){ return color; }, - [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, + [&](glm::vec3 normal){ return glm::vec3(inverseTransposeTransform * glm::vec4(normal, 0.0f)); }, [&](uint32_t index){ return index; }); MeshProxy* resultProxy = new SimpleMeshProxy(result); return meshToScriptValue(_modelScriptEngine, resultProxy); From 29485b65e568a22b6799d38db2a620c0654171b3 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 22 Dec 2017 14:35:19 +0100 Subject: [PATCH 11/31] Fixed potential crash when getting mesh from voxel item --- .../src/RenderablePolyVoxEntityItem.cpp | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 356bf3a69c..2078f7abee 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1422,27 +1422,29 @@ bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { } bool success = false; - MeshProxy* meshProxy = nullptr; - glm::mat4 transform = voxelToLocalMatrix(); - withReadLock([&] { - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); - if (!_meshReady) { - // we aren't ready to return a mesh. the caller will have to try again later. - success = false; - } else if (numVertices == 0) { - // we are ready, but there are no triangles in the mesh. - success = true; - } else { - success = true; - // the mesh will be in voxel-space. transform it into object-space - meshProxy = new SimpleMeshProxy( - _mesh->map([=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [=](glm::vec3 color){ return color; }, - [=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, - [&](uint32_t index){ return index; })); - result << meshProxy; - } - }); + if (_mesh) { + MeshProxy* meshProxy = nullptr; + glm::mat4 transform = voxelToLocalMatrix(); + withReadLock([&] { + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); + if (!_meshReady) { + // we aren't ready to return a mesh. the caller will have to try again later. + success = false; + } else if (numVertices == 0) { + // we are ready, but there are no triangles in the mesh. + success = true; + } else { + success = true; + // the mesh will be in voxel-space. transform it into object-space + meshProxy = new SimpleMeshProxy( + _mesh->map([=](glm::vec3 position) { return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, + [&](uint32_t index) { return index; })); + result << meshProxy; + } + }); + } return success; } From 1d5f65b082d00b3d2b3f2a917403b9e979b97a57 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 22 Dec 2017 15:16:39 +0100 Subject: [PATCH 12/31] Updated Mesh to support map and forEach operations on colors in RGBA8 format and normals in XYZ10W2 format --- libraries/gpu/src/gpu/Stream.cpp | 9 +++ libraries/gpu/src/gpu/Stream.h | 1 + libraries/model/src/model/Geometry.cpp | 96 +++++++++++++++++++------- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/libraries/gpu/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp index 427af1e78d..caa1ecbc06 100644 --- a/libraries/gpu/src/gpu/Stream.cpp +++ b/libraries/gpu/src/gpu/Stream.cpp @@ -92,6 +92,15 @@ bool Stream::Format::setAttribute(Slot slot, Slot channel, Frequency frequency) return true; } +Stream::Attribute Stream::Format::getAttribute(Slot slot) const { + auto attribIt = _attributes.find(slot); + if (attribIt != _attributes.end()) { + return attribIt->second; + } else { + return Attribute(); + } +} + void BufferStream::addBuffer(const BufferPointer& buffer, Offset offset, Offset stride) { _buffers.push_back(buffer); _offsets.push_back(offset); diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 336e34ecb4..5562980a91 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -112,6 +112,7 @@ public: bool setAttribute(Slot slot, Slot channel, Frequency frequency = PER_VERTEX); bool hasAttribute(Slot slot) const { return (_attributes.find(slot) != _attributes.end()); } + Attribute getAttribute(Slot slot) const; const std::string& getKey() const { return _key; } diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index f6c17adf17..210b4ae8f3 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -11,6 +11,8 @@ #include "Geometry.h" +#include + using namespace model; Mesh::Mesh() : @@ -139,13 +141,15 @@ model::MeshPointer Mesh::map(std::function vertexFunc, std::function colorFunc, std::function normalFunc, std::function indexFunc) const { + const auto vertexFormat = getVertexFormat(); + // vertex data const gpu::BufferView& vertexBufferView = getVertexBuffer(); gpu::BufferView::Index numVertices = (gpu::BufferView::Index)getNumVertices(); gpu::Resource::Size vertexSize = numVertices * sizeof(glm::vec3); - unsigned char* resultVertexData = new unsigned char[vertexSize]; - unsigned char* vertexDataCursor = resultVertexData; + std::unique_ptr resultVertexData{ new unsigned char[vertexSize] }; + unsigned char* vertexDataCursor = resultVertexData.get(); for (gpu::BufferView::Index i = 0; i < numVertices; i++) { glm::vec3 pos = vertexFunc(vertexBufferView.get(i)); @@ -159,13 +163,24 @@ model::MeshPointer Mesh::map(std::function vertexFunc, gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); gpu::Resource::Size colorSize = numColors * sizeof(glm::vec3); - unsigned char* resultColorData = new unsigned char[colorSize]; - unsigned char* colorDataCursor = resultColorData; + std::unique_ptr resultColorData{ new unsigned char[colorSize] }; + unsigned char* colorDataCursor = resultColorData.get(); + auto colorAttribute = vertexFormat->getAttribute(attributeTypeColor); - for (gpu::BufferView::Index i = 0; i < numColors; i++) { - glm::vec3 color = colorFunc(colorsBufferView.get(i)); - memcpy(colorDataCursor, &color, sizeof(color)); - colorDataCursor += sizeof(color); + if (colorAttribute._element == gpu::Element::VEC3F_XYZ) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + glm::vec3 color = colorFunc(colorsBufferView.get(i)); + memcpy(colorDataCursor, &color, sizeof(color)); + colorDataCursor += sizeof(color); + } + } else if (colorAttribute._element == gpu::Element::COLOR_RGBA_32) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + auto rawColor = colorsBufferView.get(i); + auto color = glm::vec3(glm::unpackUnorm4x8(rawColor)); + color = colorFunc(color); + memcpy(colorDataCursor, &color, sizeof(color)); + colorDataCursor += sizeof(color); + } } // normal data @@ -173,22 +188,34 @@ model::MeshPointer Mesh::map(std::function vertexFunc, const gpu::BufferView& normalsBufferView = getAttributeBuffer(attributeTypeNormal); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); gpu::Resource::Size normalSize = numNormals * sizeof(glm::vec3); - unsigned char* resultNormalData = new unsigned char[normalSize]; - unsigned char* normalDataCursor = resultNormalData; + std::unique_ptr resultNormalData{ new unsigned char[normalSize] }; + unsigned char* normalDataCursor = resultNormalData.get(); + auto normalAttribute = vertexFormat->getAttribute(attributeTypeNormal); - for (gpu::BufferView::Index i = 0; i < numNormals; i++) { - glm::vec3 normal = normalFunc(normalsBufferView.get(i)); - memcpy(normalDataCursor, &normal, sizeof(normal)); - normalDataCursor += sizeof(normal); + if (normalAttribute._element == gpu::Element::VEC3F_XYZ) { + for (gpu::BufferView::Index i = 0; i < numNormals; i++) { + glm::vec3 normal = normalFunc(normalsBufferView.get(i)); + memcpy(normalDataCursor, &normal, sizeof(normal)); + normalDataCursor += sizeof(normal); + } + } else if (normalAttribute._element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + auto packedNormal = normalsBufferView.get(i); + auto normal = glm::vec3(glm::unpackSnorm3x10_1x2(packedNormal)); + normal = normalFunc(normal); + memcpy(normalDataCursor, &normal, sizeof(normal)); + normalDataCursor += sizeof(normal); + } } + // TODO -- other attributes // face data const gpu::BufferView& indexBufferView = getIndexBuffer(); gpu::BufferView::Index numIndexes = (gpu::BufferView::Index)getNumIndices(); gpu::Resource::Size indexSize = numIndexes * sizeof(uint32_t); - unsigned char* resultIndexData = new unsigned char[indexSize]; - unsigned char* indexDataCursor = resultIndexData; + std::unique_ptr resultIndexData{ new unsigned char[indexSize] }; + unsigned char* indexDataCursor = resultIndexData.get(); for (gpu::BufferView::Index i = 0; i < numIndexes; i++) { uint32_t index = indexFunc(indexBufferView.get(i)); @@ -199,25 +226,25 @@ model::MeshPointer Mesh::map(std::function vertexFunc, model::MeshPointer result(new model::Mesh()); gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* resultVertexBuffer = new gpu::Buffer(vertexSize, resultVertexData); + gpu::Buffer* resultVertexBuffer = new gpu::Buffer(vertexSize, resultVertexData.get()); gpu::BufferPointer resultVertexBufferPointer(resultVertexBuffer); gpu::BufferView resultVertexBufferView(resultVertexBufferPointer, vertexElement); result->setVertexBuffer(resultVertexBufferView); gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* resultColorsBuffer = new gpu::Buffer(colorSize, resultColorData); + gpu::Buffer* resultColorsBuffer = new gpu::Buffer(colorSize, resultColorData.get()); gpu::BufferPointer resultColorsBufferPointer(resultColorsBuffer); gpu::BufferView resultColorsBufferView(resultColorsBufferPointer, colorElement); result->addAttribute(attributeTypeColor, resultColorsBufferView); gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* resultNormalsBuffer = new gpu::Buffer(normalSize, resultNormalData); + gpu::Buffer* resultNormalsBuffer = new gpu::Buffer(normalSize, resultNormalData.get()); gpu::BufferPointer resultNormalsBufferPointer(resultNormalsBuffer); gpu::BufferView resultNormalsBufferView(resultNormalsBufferPointer, normalElement); result->addAttribute(attributeTypeNormal, resultNormalsBufferView); gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); - gpu::Buffer* resultIndexesBuffer = new gpu::Buffer(indexSize, resultIndexData); + gpu::Buffer* resultIndexesBuffer = new gpu::Buffer(indexSize, resultIndexData.get()); gpu::BufferPointer resultIndexesBufferPointer(resultIndexesBuffer); gpu::BufferView resultIndexesBufferView(resultIndexesBufferPointer, indexElement); result->setIndexBuffer(resultIndexesBufferView); @@ -241,6 +268,8 @@ void Mesh::forEach(std::function vertexFunc, std::function colorFunc, std::function normalFunc, std::function indexFunc) { + const auto vertexFormat = getVertexFormat(); + // vertex data const gpu::BufferView& vertexBufferView = getVertexBuffer(); gpu::BufferView::Index numVertices = (gpu::BufferView::Index)getNumVertices(); @@ -252,17 +281,36 @@ void Mesh::forEach(std::function vertexFunc, int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h const gpu::BufferView& colorsBufferView = getAttributeBuffer(attributeTypeColor); gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); - for (gpu::BufferView::Index i = 0; i < numColors; i++) { - colorFunc(colorsBufferView.get(i)); + auto colorAttribute = vertexFormat->getAttribute(attributeTypeColor); + if (colorAttribute._element == gpu::Element::VEC3F_XYZ) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + colorFunc(colorsBufferView.get(i)); + } + } else if (colorAttribute._element == gpu::Element::COLOR_RGBA_32) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + auto rawColor = colorsBufferView.get(i); + auto color = glm::unpackUnorm4x8(rawColor); + colorFunc(color); + } } // normal data int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h const gpu::BufferView& normalsBufferView = getAttributeBuffer(attributeTypeNormal); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); - for (gpu::BufferView::Index i = 0; i < numNormals; i++) { - normalFunc(normalsBufferView.get(i)); + auto normalAttribute = vertexFormat->getAttribute(attributeTypeNormal); + if (normalAttribute._element == gpu::Element::VEC3F_XYZ) { + for (gpu::BufferView::Index i = 0; i < numNormals; i++) { + normalFunc(normalsBufferView.get(i)); + } + } else if (normalAttribute._element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + auto packedNormal = normalsBufferView.get(i); + auto normal = glm::unpackSnorm3x10_1x2(packedNormal); + normalFunc(normal); + } } + // TODO -- other attributes // face data From 5018edcfe00b701fdc5bcef243957a8fdd1150dd Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 22 Dec 2017 12:48:05 -0800 Subject: [PATCH 13/31] reduce calls to getVisible --- .../entities-renderer/src/RenderableEntityItem.cpp | 5 +---- libraries/entities/src/EntityItem.cpp | 10 +++++++++- libraries/entities/src/EntityItem.h | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 24de651247..3060cdb9ca 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -141,6 +141,7 @@ std::shared_ptr make_renderer(const EntityItemPointer& entity) { } EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity) { + connect(entity.get(), &EntityItem::requestRenderUpdate, this, &EntityRenderer::requestRenderUpdate); } EntityRenderer::~EntityRenderer() { } @@ -331,10 +332,6 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity return true; } - if (_visible != entity->getVisible()) { - return true; - } - if (_moving != entity->isMovingRelativeToParent()) { return true; } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 271fef33c8..5f2b260627 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -2692,9 +2692,17 @@ bool EntityItem::getVisible() const { } void EntityItem::setVisible(bool value) { + bool changed = false; withWriteLock([&] { - _visible = value; + if (_visible != value) { + changed = true; + _visible = value; + } }); + + if (changed) { + emit requestRenderUpdate(); + } } bool EntityItem::isChildOfMyAvatar() const { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 088d21e84d..06471b991b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -464,6 +464,9 @@ public: static QString _marketplacePublicKey; static void retrieveMarketplacePublicKey(); +signals: + void requestRenderUpdate(); + protected: QHash _changeHandlers; From 10d494b51de4243b1258625933c4ca6a507c26da Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 4 Jan 2018 12:32:30 -0800 Subject: [PATCH 14/31] Pagination for Recent Activity --- .../qml/hifi/commerce/purchases/Purchases.qml | 7 ++ .../qml/hifi/commerce/wallet/WalletHome.qml | 87 +++++++++++++------ interface/src/commerce/Ledger.cpp | 19 ++-- interface/src/commerce/Ledger.h | 4 +- interface/src/commerce/QmlCommerce.cpp | 4 +- interface/src/commerce/QmlCommerce.h | 2 +- 6 files changed, 82 insertions(+), 41 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index de66be4a88..068403e098 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -472,6 +472,13 @@ Rectangle { } } } + + onAtYEndChanged: { + if (purchasesContentsList.atYEnd) { + console.log("User scrolled to the bottom of 'My Purchases'."); + // Grab next page of results and append to model + } + } } Item { diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 42ee44d584..16ad999441 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -25,8 +25,11 @@ Item { HifiConstants { id: hifi; } id: root; - property bool historyReceived: false; + property bool initialHistoryReceived: false; + property bool historyRequestPending: true; + property bool noMoreHistoryData: false; property int pendingCount: 0; + property int currentHistoryPage: 1; Connections { target: Commerce; @@ -36,32 +39,50 @@ Item { } onHistoryResult : { - historyReceived = true; - if (result.status === 'success') { - var sameItemCount = 0; - tempTransactionHistoryModel.clear(); - - tempTransactionHistoryModel.append(result.data.history); - - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - if (!transactionHistoryModel.get(i)) { - sameItemCount = -1; - break; - } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && - tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { - sameItemCount++; - } - } + root.initialHistoryReceived = true; + root.historyRequestPending = false; - if (sameItemCount !== tempTransactionHistoryModel.count) { - transactionHistoryModel.clear(); + if (result.status === 'success') { + if (result.data.history.length === 0) { + root.noMoreHistoryData = true; + } else if (root.currentHistoryPage === 1) { + var sameItemCount = 0; + tempTransactionHistoryModel.clear(); + + tempTransactionHistoryModel.append(result.data.history); + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); + if (!transactionHistoryModel.get(i)) { + sameItemCount = -1; + break; + } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && + tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { + sameItemCount++; + } + } + + if (sameItemCount !== tempTransactionHistoryModel.count) { + transactionHistoryModel.clear(); + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { + transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); + } + calculatePendingAndInvalidated(); + } + } else { + // This prevents data from being displayed out-of-order, + // but may also result in missing pages of data when scrolling quickly... + if (root.currentHistoryPage === result.current_page) { + transactionHistoryModel.append(result.data.history); + calculatePendingAndInvalidated(); } - calculatePendingAndInvalidated(); } } - refreshTimer.start(); + + // Only auto-refresh if the user hasn't scrolled + // and there is more data to grab + if (root.currentHistoryPage === 1 && !root.noMoreHistoryData) { + refreshTimer.start(); + } } } @@ -134,9 +155,13 @@ Item { onVisibleChanged: { if (visible) { - historyReceived = false; + transactionHistoryModel.clear(); Commerce.balance(); - Commerce.history(); + initialHistoryReceived = false; + root.currentHistoryPage = 1; + root.noMoreHistoryData = false; + root.historyRequestPending = true; + Commerce.history(root.currentHistoryPage); } else { refreshTimer.stop(); } @@ -164,9 +189,10 @@ Item { id: refreshTimer; interval: 4000; onTriggered: { - console.log("Refreshing Wallet Home..."); + console.log("Refreshing 1st Page of Recent Activity..."); + root.historyRequestPending = true; Commerce.balance(); - Commerce.history(); + Commerce.history(1); } } @@ -241,7 +267,7 @@ Item { anchors.right: parent.right; Item { - visible: transactionHistoryModel.count === 0 && root.historyReceived; + visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived; anchors.centerIn: parent; width: parent.width - 12; height: parent.height; @@ -364,7 +390,12 @@ Item { onAtYEndChanged: { if (transactionHistory.atYEnd) { console.log("User scrolled to the bottom of 'Recent Activity'."); - // Grab next page of results and append to model + if (!root.historyRequestPending && !root.noMoreHistoryData) { + // Grab next page of results and append to model + root.historyRequestPending = true; + Commerce.history(++root.currentHistoryPage); + console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity..."); + } } } } diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index d7d36dabf6..b2ac92e85a 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -72,11 +72,11 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } -void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { +void Ledger::keysQuery(const QString& endpoint, QJsonObject& requestParams, const QString& success, const QString& fail) { auto wallet = DependencyManager::get(); - QJsonObject request; - request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); - send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, request); + requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + + send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams); } void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) { @@ -104,11 +104,11 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) { } void Ledger::balance(const QStringList& keys) { - keysQuery("balance", "balanceSuccess", "balanceFailure"); + keysQuery("balance", QJsonObject(), "balanceSuccess", "balanceFailure"); } void Ledger::inventory(const QStringList& keys) { - keysQuery("inventory", "inventorySuccess", "inventoryFailure"); + keysQuery("inventory", QJsonObject(), "inventorySuccess", "inventoryFailure"); } QString amountString(const QString& label, const QString&color, const QJsonValue& moneyValue, const QJsonValue& certsValue) { @@ -176,8 +176,11 @@ void Ledger::historyFailure(QNetworkReply& reply) { failResponse("history", reply); } -void Ledger::history(const QStringList& keys) { - keysQuery("history", "historySuccess", "historyFailure"); +void Ledger::history(const QStringList& keys, const QString& pageNumber) { + QJsonObject params; + params["per_page"] = 7; + params["page"] = pageNumber; + keysQuery("history", params, "historySuccess", "historyFailure"); } // The api/failResponse is called just for the side effect of logging. diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 42eb0ffc49..db3d1f064a 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -29,7 +29,7 @@ public: bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); void inventory(const QStringList& keys); - void history(const QStringList& keys); + void history(const QStringList& keys, const QString& pageNumber); void account(); void reset(); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); @@ -79,7 +79,7 @@ private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); QJsonObject failResponse(const QString& label, QNetworkReply& reply); void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); - void keysQuery(const QString& endpoint, const QString& success, const QString& fail); + void keysQuery(const QString& endpoint, QJsonObject& extraRequestParams, const QString& success, const QString& fail); void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false); }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 62e87f9c66..36608fe0a6 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -96,12 +96,12 @@ void QmlCommerce::inventory() { } } -void QmlCommerce::history() { +void QmlCommerce::history(const QString& pageNumber) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->history(cachedPublicKeys); + ledger->history(cachedPublicKeys, pageNumber); } } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index c53e73d565..88223aacb0 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -60,7 +60,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); - Q_INVOKABLE void history(); + Q_INVOKABLE void history(const QString& pageNumber); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); Q_INVOKABLE void resetLocalWalletOnly(); From 14447c26a0c69be9505391ea5903778777322bfa Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 4 Jan 2018 13:47:31 -0800 Subject: [PATCH 15/31] Prevent visual data loss --- .../qml/hifi/commerce/wallet/WalletHome.qml | 54 ++++++++++++++++--- interface/src/commerce/Ledger.cpp | 1 + 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 16ad999441..1fd304246f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -30,6 +30,7 @@ Item { property bool noMoreHistoryData: false; property int pendingCount: 0; property int currentHistoryPage: 1; + property var pagesAlreadyAdded: new Array(); Connections { target: Commerce; @@ -43,6 +44,8 @@ Item { root.historyRequestPending = false; if (result.status === 'success') { + var currentPage = parseInt(result.current_page); + if (result.data.history.length === 0) { root.noMoreHistoryData = true; } else if (root.currentHistoryPage === 1) { @@ -69,10 +72,43 @@ Item { calculatePendingAndInvalidated(); } } else { - // This prevents data from being displayed out-of-order, - // but may also result in missing pages of data when scrolling quickly... - if (root.currentHistoryPage === result.current_page) { - transactionHistoryModel.append(result.data.history); + if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) { + console.log("Page " + currentPage + " of history has already been added to the list."); + } else { + // First, add the history result to a temporary model + tempTransactionHistoryModel.clear(); + tempTransactionHistoryModel.append(result.data.history); + + // Make a note that we've already added this page to the model... + root.pagesAlreadyAdded.push(currentPage); + + var insertionIndex = 0; + // If there's nothing in the model right now, we don't need to modify insertionIndex. + if (transactionHistoryModel.count !== 0) { + var currentIteratorPage; + // Search through the whole transactionHistoryModel and look for the insertion point. + // The insertion point is found when the result page from the server is less than + // the page that the current item came from, OR when we've reached the end of the whole model. + for (var i = 0; i < transactionHistoryModel.count; i++) { + currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage; + + if (currentPage < currentIteratorPage) { + insertionIndex = i; + break; + } else if (i === transactionHistoryModel.count - 1) { + insertionIndex = i + 1; + break; + } + } + } + + // Go through the results we just got back from the server, setting the "resultIsFromPage" + // property of those results and adding them to the main model. + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { + tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage); + transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i)) + } + calculatePendingAndInvalidated(); } } @@ -189,10 +225,12 @@ Item { id: refreshTimer; interval: 4000; onTriggered: { - console.log("Refreshing 1st Page of Recent Activity..."); - root.historyRequestPending = true; - Commerce.balance(); - Commerce.history(1); + if (root.currentHistoryPage === 1) { + console.log("Refreshing 1st Page of Recent Activity..."); + root.historyRequestPending = true; + Commerce.balance(); + Commerce.history(1); + } } } diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index b2ac92e85a..41ef1a1932 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -169,6 +169,7 @@ void Ledger::historySuccess(QNetworkReply& reply) { QJsonObject newDataData; newDataData["history"] = newHistoryArray; newData["data"] = newDataData; + newData["current_page"] = data["current_page"].toInt(); emit historyResult(newData); } From 4232bc02004e5e9e4e93dc4255e351fc46c7304b Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 4 Jan 2018 14:42:05 -0800 Subject: [PATCH 16/31] Bump per_page to 100 --- interface/src/commerce/Ledger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 41ef1a1932..5d765e2c32 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -179,7 +179,7 @@ void Ledger::historyFailure(QNetworkReply& reply) { void Ledger::history(const QStringList& keys, const QString& pageNumber) { QJsonObject params; - params["per_page"] = 7; + params["per_page"] = 100; params["page"] = pageNumber; keysQuery("history", params, "historySuccess", "historyFailure"); } From 4612ef3d7cdf5a1d32fcb3f5f3576e34b1cfdb6b Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 4 Jan 2018 15:41:01 -0800 Subject: [PATCH 17/31] Use atYBeginning --- interface/resources/qml/hifi/commerce/wallet/WalletHome.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 1fd304246f..af708b4031 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -116,7 +116,7 @@ Item { // Only auto-refresh if the user hasn't scrolled // and there is more data to grab - if (root.currentHistoryPage === 1 && !root.noMoreHistoryData) { + if (transactionHistory.atYBeginning && !root.noMoreHistoryData) { refreshTimer.start(); } } @@ -225,7 +225,7 @@ Item { id: refreshTimer; interval: 4000; onTriggered: { - if (root.currentHistoryPage === 1) { + if (transactionHistory.atYBeginning) { console.log("Refreshing 1st Page of Recent Activity..."); root.historyRequestPending = true; Commerce.balance(); From cdd62c7119b161d46e09d0385c0adc0c6ef8bbe5 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 8 Jan 2018 11:29:41 -0800 Subject: [PATCH 18/31] I don't think auto-refresh and pagination of inventory is possible --- .../qml/hifi/commerce/checkout/Checkout.qml | 4 +- .../qml/hifi/commerce/purchases/Purchases.qml | 173 +++++++++++++----- interface/src/commerce/Ledger.cpp | 7 +- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/QmlCommerce.cpp | 4 +- interface/src/commerce/QmlCommerce.h | 2 +- 6 files changed, 134 insertions(+), 58 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 4de09c1bf3..d7c86566a4 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -117,7 +117,7 @@ Rectangle { } onItemIdChanged: { - Commerce.inventory(); + Commerce.inventory(1); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } @@ -945,7 +945,7 @@ Rectangle { } root.balanceReceived = false; root.purchasesReceived = false; - Commerce.inventory(); + Commerce.inventory(1); Commerce.balance(); } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 068403e098..25aeadaa80 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -30,12 +30,16 @@ Rectangle { property string activeView: "initialize"; property string referrerURL: ""; property bool securityImageResultReceived: false; - property bool purchasesReceived: false; property bool punctuationMode: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); - property bool pendingInventoryReply: true; property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; + property bool initialPurchasesReceived: false; + property bool pendingPurchasesReply: true; + property bool noMorePurchasesData: false; + property var pagesAlreadyAdded: new Array(); + property int currentPurchasesPage: 1; + // Style color: hifi.colors.white; Connections { @@ -61,7 +65,9 @@ Rectangle { root.activeView = "firstUseTutorial"; } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; - Commerce.inventory(); + root.initialPurchasesReceived = false; + root.pendingPurchasesReply = true; + Commerce.inventory(1); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -77,35 +83,87 @@ Rectangle { } onInventoryResult: { - purchasesReceived = true; - - if (root.pendingInventoryReply) { - inventoryTimer.start(); - } - if (result.status !== 'success') { console.log("Failed to get purchases", result.message); } else { - var inventoryResult = processInventoryResult(result.data.assets); + var currentPage = parseInt(result.current_page); - purchasesModel.clear(); - purchasesModel.append(inventoryResult); + console.log("ZRF length: " + result.data.assets.length + " page: " + currentPage); + if (result.data.assets.length === 0) { + root.noMorePurchasesData = true; + } else if (root.currentPurchasesPage === 1) { + var purchasesResult = processPurchasesResult(result.data.assets); - if (previousPurchasesModel.count !== 0) { - checkIfAnyItemStatusChanged(); - } else { - // Fill statusChanged default value - // Not doing this results in the default being true... - for (var i = 0; i < purchasesModel.count; i++) { - purchasesModel.setProperty(i, "statusChanged", false); + purchasesModel.clear(); + purchasesModel.append(purchasesResult); + + if (previousPurchasesModel.count !== 0) { + checkIfAnyItemStatusChanged(); + } else { + // Fill statusChanged default value + // Not doing this results in the default being true... + for (var i = 0; i < purchasesModel.count; i++) { + purchasesModel.setProperty(i, "statusChanged", false); + } } + previousPurchasesModel.append(purchasesResult); + + buildFilteredPurchasesModel(); + } else { + // First, add the purchases result to a temporary model + tempPurchasesModel.clear(); + tempPurchasesModel.append(processPurchasesResult(result.data.assets)); + + // Make a note that we've already added this page to the model... + root.pagesAlreadyAdded.push(currentPage); + + var insertionIndex = 0; + // If there's nothing in the model right now, we don't need to modify insertionIndex. + if (purchasesModel.count !== 0) { + var currentIteratorPage; + // Search through the whole purchasesModel and look for the insertion point. + // The insertion point is found when the result page from the server is less than + // the page that the current item came from, OR when we've reached the end of the whole model. + for (var i = 0; i < purchasesModel.count; i++) { + currentIteratorPage = purchasesModel.get(i).resultIsFromPage; + + if (currentPage < currentIteratorPage) { + insertionIndex = i; + break; + } else if (i === purchasesModel.count - 1) { + insertionIndex = i + 1; + break; + } + } + } + + // Go through the results we just got back from the server, setting the "resultIsFromPage" + // property of those results and adding them to the main model. + for (var i = 0; i < tempPurchasesModel.count; i++) { + tempPurchasesModel.setProperty(i, "resultIsFromPage", currentPage); + purchasesModel.insert(i + insertionIndex, tempPurchasesModel.get(i)) + } + + buildFilteredPurchasesModel(); } - previousPurchasesModel.append(inventoryResult); - - buildFilteredPurchasesModel(); } + + root.pendingPurchasesReply = false; - root.pendingInventoryReply = false; + if (filteredPurchasesModel.count === 0 && !root.noMorePurchasesData) { + root.initialPurchasesReceived = false; + root.pendingPurchasesReply = true; + Commerce.inventory(++root.currentPurchasesPage); + console.log("Fetching Page " + root.currentPurchasesPage + " of Purchases..."); + // Only auto-refresh if the user hasn't scrolled + // and there is more data to grab + } else { + root.initialPurchasesReceived = true; + } + + if (purchasesContentsList.atYBeginning && !root.noMorePurchasesData) { + purchasesTimer.start(); + } } } @@ -197,7 +255,8 @@ Rectangle { Component.onCompleted: { securityImageResultReceived = false; - purchasesReceived = false; + root.initialPurchasesReceived = false; + root.pendingPurchasesReply = true; Commerce.getWalletStatus(); } } @@ -255,7 +314,9 @@ Rectangle { case 'tutorial_finished': Settings.setValue("isFirstUseOfPurchases", false); root.activeView = "purchasesMain"; - Commerce.inventory(); + root.initialPurchasesReceived = false; + root.pendingPurchasesReply = true; + Commerce.inventory(1); break; } } @@ -318,6 +379,12 @@ Rectangle { onTextChanged: { buildFilteredPurchasesModel(); + if (filteredPurchasesModel.count === 0 && !root.noMorePurchasesData) { + root.initialPurchasesReceived = false; + root.pendingPurchasesReply = true; + Commerce.inventory(++root.currentPurchasesPage); + console.log("Fetching Page " + root.currentPurchasesPage + " of Purchases..."); + } } onAccepted: { @@ -476,14 +543,19 @@ Rectangle { onAtYEndChanged: { if (purchasesContentsList.atYEnd) { console.log("User scrolled to the bottom of 'My Purchases'."); - // Grab next page of results and append to model + if (!root.pendingPurchasesReply && !root.noMorePurchasesData) { + // Grab next page of results and append to model + root.pendingPurchasesReply = true; + Commerce.inventory(++root.currentPurchasesPage); + console.log("Fetching Page " + root.currentPurchasesPage + " of Purchases..."); + } } } } Item { id: noItemsAlertContainer; - visible: !purchasesContentsList.visible && root.purchasesReceived && root.isShowingMyItems && filterBar.text === ""; + visible: !purchasesContentsList.visible && root.initialPurchasesReceived && root.isShowingMyItems && filterBar.text === "" && root.noMorePurchasesData; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -529,7 +601,7 @@ Rectangle { Item { id: noPurchasesAlertContainer; - visible: !purchasesContentsList.visible && root.purchasesReceived && !root.isShowingMyItems && filterBar.text === ""; + visible: !purchasesContentsList.visible && root.initialPurchasesReceived && !root.isShowingMyItems && filterBar.text === "" && root.noMorePurchasesData; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -590,19 +662,19 @@ Rectangle { onVisibleChanged: { if (!visible) { - inventoryTimer.stop(); + purchasesTimer.stop(); } } Timer { - id: inventoryTimer; + id: purchasesTimer; interval: 4000; // Change this back to 90000 after demo //interval: 90000; onTriggered: { - if (root.activeView === "purchasesMain" && !root.pendingInventoryReply) { - console.log("Refreshing Purchases..."); - root.pendingInventoryReply = true; - Commerce.inventory(); + if (root.activeView === "purchasesMain" && purchasesContentsList.atYBeginning && !root.pendingPurchasesReply && !root.noMorePurchasesData) { + console.log("Refreshing 1st Page of Purchases..."); + root.pendingPurchasesReply = true; + Commerce.inventory(1); } } } @@ -611,15 +683,15 @@ Rectangle { // FUNCTION DEFINITIONS START // - function processInventoryResult(inventory) { - for (var i = 0; i < inventory.length; i++) { - if (inventory[i].status.length > 1) { - console.log("WARNING: Inventory result index " + i + " has a status of length >1!") + function processPurchasesResult(purchases) { + for (var i = 0; i < purchases.length; i++) { + if (purchases[i].status.length > 1) { + console.log("WARNING: Purchases result index " + i + " has a status of length >1!") } - inventory[i].status = inventory[i].status[0]; - inventory[i].categories = inventory[i].categories.join(';'); + purchases[i].status = purchases[i].status[0]; + purchases[i].categories = purchases[i].categories.join(';'); } - return inventory; + return purchases; } function populateDisplayedItemCounts() { @@ -660,17 +732,18 @@ Rectangle { } } - for (var i = 0; i < tempPurchasesModel.count; i++) { - if (!filteredPurchasesModel.get(i)) { - sameItemCount = -1; - break; - } else if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId && - tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number && - tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) { - sameItemCount++; + if (tempPurchasesModel.count === 0 || filteredPurchasesModel.count === 0) { + sameItemCount = -1; + } else { + for (var i = 0; i < tempPurchasesModel.count; i++) { + if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId && + tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number && + tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) { + sameItemCount++; + } } } - + if (sameItemCount !== tempPurchasesModel.count) { filteredPurchasesModel.clear(); for (var i = 0; i < tempPurchasesModel.count; i++) { diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 5d765e2c32..f02a70376d 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -107,8 +107,11 @@ void Ledger::balance(const QStringList& keys) { keysQuery("balance", QJsonObject(), "balanceSuccess", "balanceFailure"); } -void Ledger::inventory(const QStringList& keys) { - keysQuery("inventory", QJsonObject(), "inventorySuccess", "inventoryFailure"); +void Ledger::inventory(const QStringList& keys, const QString& pageNumber) { + QJsonObject params; + params["per_page"] = 1; + params["page"] = pageNumber; + keysQuery("inventory", params, "inventorySuccess", "inventoryFailure"); } QString amountString(const QString& label, const QString&color, const QJsonValue& moneyValue, const QJsonValue& certsValue) { diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index db3d1f064a..6770e47e6e 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -28,7 +28,7 @@ public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); - void inventory(const QStringList& keys); + void inventory(const QStringList& keys, const QString& pageNumber); void history(const QStringList& keys, const QString& pageNumber); void account(); void reset(); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 36608fe0a6..354335668f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -87,12 +87,12 @@ void QmlCommerce::balance() { } } -void QmlCommerce::inventory() { +void QmlCommerce::inventory(const QString& pageNumber) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->inventory(cachedPublicKeys); + ledger->inventory(cachedPublicKeys, pageNumber); } } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 88223aacb0..891c838b68 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -59,7 +59,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); - Q_INVOKABLE void inventory(); + Q_INVOKABLE void inventory(const QString& pageNumber); Q_INVOKABLE void history(const QString& pageNumber); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); From dcfceab0789d5b7379f200452cfc5adbe923b7a1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 8 Jan 2018 12:03:56 -0800 Subject: [PATCH 19/31] Remove all work done on paginating purchases --- .../qml/hifi/commerce/checkout/Checkout.qml | 4 +- .../qml/hifi/commerce/purchases/Purchases.qml | 176 +++++------------- interface/src/commerce/Ledger.cpp | 7 +- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/QmlCommerce.cpp | 4 +- interface/src/commerce/QmlCommerce.h | 2 +- 6 files changed, 56 insertions(+), 139 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index d7c86566a4..4de09c1bf3 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -117,7 +117,7 @@ Rectangle { } onItemIdChanged: { - Commerce.inventory(1); + Commerce.inventory(); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } @@ -945,7 +945,7 @@ Rectangle { } root.balanceReceived = false; root.purchasesReceived = false; - Commerce.inventory(1); + Commerce.inventory(); Commerce.balance(); } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 25aeadaa80..de66be4a88 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -30,16 +30,12 @@ Rectangle { property string activeView: "initialize"; property string referrerURL: ""; property bool securityImageResultReceived: false; + property bool purchasesReceived: false; property bool punctuationMode: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); + property bool pendingInventoryReply: true; property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; - property bool initialPurchasesReceived: false; - property bool pendingPurchasesReply: true; - property bool noMorePurchasesData: false; - property var pagesAlreadyAdded: new Array(); - property int currentPurchasesPage: 1; - // Style color: hifi.colors.white; Connections { @@ -65,9 +61,7 @@ Rectangle { root.activeView = "firstUseTutorial"; } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; - root.initialPurchasesReceived = false; - root.pendingPurchasesReply = true; - Commerce.inventory(1); + Commerce.inventory(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -83,87 +77,35 @@ Rectangle { } onInventoryResult: { + purchasesReceived = true; + + if (root.pendingInventoryReply) { + inventoryTimer.start(); + } + if (result.status !== 'success') { console.log("Failed to get purchases", result.message); } else { - var currentPage = parseInt(result.current_page); + var inventoryResult = processInventoryResult(result.data.assets); - console.log("ZRF length: " + result.data.assets.length + " page: " + currentPage); - if (result.data.assets.length === 0) { - root.noMorePurchasesData = true; - } else if (root.currentPurchasesPage === 1) { - var purchasesResult = processPurchasesResult(result.data.assets); + purchasesModel.clear(); + purchasesModel.append(inventoryResult); - purchasesModel.clear(); - purchasesModel.append(purchasesResult); - - if (previousPurchasesModel.count !== 0) { - checkIfAnyItemStatusChanged(); - } else { - // Fill statusChanged default value - // Not doing this results in the default being true... - for (var i = 0; i < purchasesModel.count; i++) { - purchasesModel.setProperty(i, "statusChanged", false); - } - } - previousPurchasesModel.append(purchasesResult); - - buildFilteredPurchasesModel(); + if (previousPurchasesModel.count !== 0) { + checkIfAnyItemStatusChanged(); } else { - // First, add the purchases result to a temporary model - tempPurchasesModel.clear(); - tempPurchasesModel.append(processPurchasesResult(result.data.assets)); - - // Make a note that we've already added this page to the model... - root.pagesAlreadyAdded.push(currentPage); - - var insertionIndex = 0; - // If there's nothing in the model right now, we don't need to modify insertionIndex. - if (purchasesModel.count !== 0) { - var currentIteratorPage; - // Search through the whole purchasesModel and look for the insertion point. - // The insertion point is found when the result page from the server is less than - // the page that the current item came from, OR when we've reached the end of the whole model. - for (var i = 0; i < purchasesModel.count; i++) { - currentIteratorPage = purchasesModel.get(i).resultIsFromPage; - - if (currentPage < currentIteratorPage) { - insertionIndex = i; - break; - } else if (i === purchasesModel.count - 1) { - insertionIndex = i + 1; - break; - } - } + // Fill statusChanged default value + // Not doing this results in the default being true... + for (var i = 0; i < purchasesModel.count; i++) { + purchasesModel.setProperty(i, "statusChanged", false); } - - // Go through the results we just got back from the server, setting the "resultIsFromPage" - // property of those results and adding them to the main model. - for (var i = 0; i < tempPurchasesModel.count; i++) { - tempPurchasesModel.setProperty(i, "resultIsFromPage", currentPage); - purchasesModel.insert(i + insertionIndex, tempPurchasesModel.get(i)) - } - - buildFilteredPurchasesModel(); } - } - - root.pendingPurchasesReply = false; + previousPurchasesModel.append(inventoryResult); - if (filteredPurchasesModel.count === 0 && !root.noMorePurchasesData) { - root.initialPurchasesReceived = false; - root.pendingPurchasesReply = true; - Commerce.inventory(++root.currentPurchasesPage); - console.log("Fetching Page " + root.currentPurchasesPage + " of Purchases..."); - // Only auto-refresh if the user hasn't scrolled - // and there is more data to grab - } else { - root.initialPurchasesReceived = true; - } - - if (purchasesContentsList.atYBeginning && !root.noMorePurchasesData) { - purchasesTimer.start(); + buildFilteredPurchasesModel(); } + + root.pendingInventoryReply = false; } } @@ -255,8 +197,7 @@ Rectangle { Component.onCompleted: { securityImageResultReceived = false; - root.initialPurchasesReceived = false; - root.pendingPurchasesReply = true; + purchasesReceived = false; Commerce.getWalletStatus(); } } @@ -314,9 +255,7 @@ Rectangle { case 'tutorial_finished': Settings.setValue("isFirstUseOfPurchases", false); root.activeView = "purchasesMain"; - root.initialPurchasesReceived = false; - root.pendingPurchasesReply = true; - Commerce.inventory(1); + Commerce.inventory(); break; } } @@ -379,12 +318,6 @@ Rectangle { onTextChanged: { buildFilteredPurchasesModel(); - if (filteredPurchasesModel.count === 0 && !root.noMorePurchasesData) { - root.initialPurchasesReceived = false; - root.pendingPurchasesReply = true; - Commerce.inventory(++root.currentPurchasesPage); - console.log("Fetching Page " + root.currentPurchasesPage + " of Purchases..."); - } } onAccepted: { @@ -539,23 +472,11 @@ Rectangle { } } } - - onAtYEndChanged: { - if (purchasesContentsList.atYEnd) { - console.log("User scrolled to the bottom of 'My Purchases'."); - if (!root.pendingPurchasesReply && !root.noMorePurchasesData) { - // Grab next page of results and append to model - root.pendingPurchasesReply = true; - Commerce.inventory(++root.currentPurchasesPage); - console.log("Fetching Page " + root.currentPurchasesPage + " of Purchases..."); - } - } - } } Item { id: noItemsAlertContainer; - visible: !purchasesContentsList.visible && root.initialPurchasesReceived && root.isShowingMyItems && filterBar.text === "" && root.noMorePurchasesData; + visible: !purchasesContentsList.visible && root.purchasesReceived && root.isShowingMyItems && filterBar.text === ""; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -601,7 +522,7 @@ Rectangle { Item { id: noPurchasesAlertContainer; - visible: !purchasesContentsList.visible && root.initialPurchasesReceived && !root.isShowingMyItems && filterBar.text === "" && root.noMorePurchasesData; + visible: !purchasesContentsList.visible && root.purchasesReceived && !root.isShowingMyItems && filterBar.text === ""; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -662,19 +583,19 @@ Rectangle { onVisibleChanged: { if (!visible) { - purchasesTimer.stop(); + inventoryTimer.stop(); } } Timer { - id: purchasesTimer; + id: inventoryTimer; interval: 4000; // Change this back to 90000 after demo //interval: 90000; onTriggered: { - if (root.activeView === "purchasesMain" && purchasesContentsList.atYBeginning && !root.pendingPurchasesReply && !root.noMorePurchasesData) { - console.log("Refreshing 1st Page of Purchases..."); - root.pendingPurchasesReply = true; - Commerce.inventory(1); + if (root.activeView === "purchasesMain" && !root.pendingInventoryReply) { + console.log("Refreshing Purchases..."); + root.pendingInventoryReply = true; + Commerce.inventory(); } } } @@ -683,15 +604,15 @@ Rectangle { // FUNCTION DEFINITIONS START // - function processPurchasesResult(purchases) { - for (var i = 0; i < purchases.length; i++) { - if (purchases[i].status.length > 1) { - console.log("WARNING: Purchases result index " + i + " has a status of length >1!") + function processInventoryResult(inventory) { + for (var i = 0; i < inventory.length; i++) { + if (inventory[i].status.length > 1) { + console.log("WARNING: Inventory result index " + i + " has a status of length >1!") } - purchases[i].status = purchases[i].status[0]; - purchases[i].categories = purchases[i].categories.join(';'); + inventory[i].status = inventory[i].status[0]; + inventory[i].categories = inventory[i].categories.join(';'); } - return purchases; + return inventory; } function populateDisplayedItemCounts() { @@ -732,18 +653,17 @@ Rectangle { } } - if (tempPurchasesModel.count === 0 || filteredPurchasesModel.count === 0) { - sameItemCount = -1; - } else { - for (var i = 0; i < tempPurchasesModel.count; i++) { - if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId && - tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number && - tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) { - sameItemCount++; - } + for (var i = 0; i < tempPurchasesModel.count; i++) { + if (!filteredPurchasesModel.get(i)) { + sameItemCount = -1; + break; + } else if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId && + tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number && + tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) { + sameItemCount++; } } - + if (sameItemCount !== tempPurchasesModel.count) { filteredPurchasesModel.clear(); for (var i = 0; i < tempPurchasesModel.count; i++) { diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index f02a70376d..5d765e2c32 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -107,11 +107,8 @@ void Ledger::balance(const QStringList& keys) { keysQuery("balance", QJsonObject(), "balanceSuccess", "balanceFailure"); } -void Ledger::inventory(const QStringList& keys, const QString& pageNumber) { - QJsonObject params; - params["per_page"] = 1; - params["page"] = pageNumber; - keysQuery("inventory", params, "inventorySuccess", "inventoryFailure"); +void Ledger::inventory(const QStringList& keys) { + keysQuery("inventory", QJsonObject(), "inventorySuccess", "inventoryFailure"); } QString amountString(const QString& label, const QString&color, const QJsonValue& moneyValue, const QJsonValue& certsValue) { diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 6770e47e6e..db3d1f064a 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -28,7 +28,7 @@ public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); - void inventory(const QStringList& keys, const QString& pageNumber); + void inventory(const QStringList& keys); void history(const QStringList& keys, const QString& pageNumber); void account(); void reset(); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 354335668f..36608fe0a6 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -87,12 +87,12 @@ void QmlCommerce::balance() { } } -void QmlCommerce::inventory(const QString& pageNumber) { +void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->inventory(cachedPublicKeys, pageNumber); + ledger->inventory(cachedPublicKeys); } } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 891c838b68..88223aacb0 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -59,7 +59,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); - Q_INVOKABLE void inventory(const QString& pageNumber); + Q_INVOKABLE void inventory(); Q_INVOKABLE void history(const QString& pageNumber); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); From 611f25d542d4cf61f4e372f287700cfcede96046 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 8 Jan 2018 13:07:00 -0800 Subject: [PATCH 20/31] Improve scrolling thru My Purchases --- .../qml/hifi/commerce/purchases/Purchases.qml | 23 +++++++++++++++---- .../qml/hifi/commerce/wallet/WalletHome.qml | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index de66be4a88..87b784bc4e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -36,6 +36,7 @@ Rectangle { property bool pendingInventoryReply: true; property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; + property int pendingItemCount: 0; // Style color: hifi.colors.white; Connections { @@ -79,18 +80,22 @@ Rectangle { onInventoryResult: { purchasesReceived = true; - if (root.pendingInventoryReply) { - inventoryTimer.start(); - } - if (result.status !== 'success') { console.log("Failed to get purchases", result.message); - } else { + } else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling var inventoryResult = processInventoryResult(result.data.assets); + var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex; purchasesModel.clear(); purchasesModel.append(inventoryResult); + root.pendingItemCount = 0; + for (var i = 0; i < purchasesModel.count; i++) { + if (purchasesModel.get(i).status === "pending") { + root.pendingItemCount++; + } + } + if (previousPurchasesModel.count !== 0) { checkIfAnyItemStatusChanged(); } else { @@ -103,6 +108,12 @@ Rectangle { previousPurchasesModel.append(inventoryResult); buildFilteredPurchasesModel(); + + purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning); + } + + if (root.pendingInventoryReply && root.pendingItemCount > 0) { + inventoryTimer.start(); } root.pendingInventoryReply = false; @@ -419,6 +430,8 @@ Rectangle { visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0); clip: true; model: filteredPurchasesModel; + snapMode: ListView.SnapToItem; + highlightRangeMode: ListView.StrictlyEnforceRange; // Anchors anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom; anchors.topMargin: 12; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index af708b4031..780e08caf8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -48,6 +48,7 @@ Item { if (result.data.history.length === 0) { root.noMoreHistoryData = true; + console.log("No more data to retrieve from Commerce.history() endpoint.") } else if (root.currentHistoryPage === 1) { var sameItemCount = 0; tempTransactionHistoryModel.clear(); From b2b1807490b7732fb2289722d148a6fe748afc31 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 8 Jan 2018 15:37:06 -0800 Subject: [PATCH 21/31] Changes per code review. --- .../entities-renderer/src/RenderableZoneEntityItem.cpp | 4 ---- .../entities-renderer/src/RenderableZoneEntityItem.h | 2 -- libraries/entities/src/EntityTree.cpp | 2 +- libraries/entities/src/KeyLightPropertyGroup.cpp | 3 +-- libraries/entities/src/ZoneEntityItem.cpp | 8 ++++---- libraries/networking/src/udt/PacketHeaders.h | 2 +- libraries/render-utils/src/LightPayload.h | 1 - 7 files changed, 7 insertions(+), 15 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index ae00f168c6..95a05f6973 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -414,7 +414,6 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) { _ambientTextureURL = ambientUrl; if (_ambientTextureURL.isEmpty()) { - _validAmbientTexture = false; _pendingAmbientTexture = false; _ambientTexture.clear(); @@ -442,7 +441,6 @@ void ZoneEntityRenderer::updateAmbientMap() { _ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY); } editAmbientLight()->setAmbientMap(texture); - _validAmbientTexture = true; } else { qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL(); } @@ -458,7 +456,6 @@ void ZoneEntityRenderer::setSkyboxURL(const QString& skyboxUrl) { _skyboxTextureURL = skyboxUrl; if (_skyboxTextureURL.isEmpty()) { - _validSkyboxTexture = false; _pendingSkyboxTexture = false; _skyboxTexture.clear(); @@ -478,7 +475,6 @@ void ZoneEntityRenderer::updateSkyboxMap() { auto texture = _skyboxTexture->getGPUTexture(); if (texture) { editSkybox()->setCubemap(texture); - _validSkyboxTexture = true; } else { qCDebug(entitiesrenderer) << "Failed to load Skybox texture:" << _skyboxTexture->getURL(); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 744f1823fc..c7fbd8c405 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -119,12 +119,10 @@ private: QString _ambientTextureURL; NetworkTexturePointer _ambientTexture; bool _pendingAmbientTexture{ false }; - bool _validAmbientTexture{ false }; QString _skyboxTextureURL; NetworkTexturePointer _skyboxTexture; bool _pendingSkyboxTexture{ false }; - bool _validSkyboxTexture{ false }; QString _proceduralUserData; Transform _renderTransform; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 07d004358a..7e31e45bf0 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2291,7 +2291,7 @@ bool EntityTree::readFromMap(QVariantMap& map) { if (entityMap.contains("backgroundMode") && (entityMap["backgroundMode"].toString() == "nothing")) { properties.setSkyboxMode(COMPONENT_MODE_INHERIT); } else { - // either the background mode field is missing (shouldn't happen) or the background mode is "skybox" + // either the background mode field is missing (shouldn't happen) or the background mode is "skybox" properties.setSkyboxMode(COMPONENT_MODE_ENABLED); } } diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index 61d48f7cb1..c476b4c23c 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -82,7 +82,7 @@ bool KeyLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, return true; } -bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, +bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, int& processedBytes) { int bytesRead = 0; @@ -92,7 +92,6 @@ bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFl READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_COLOR, Color); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_INTENSITY, Intensity); diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 27b2122511..ac0e61cafd 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -362,7 +362,7 @@ void ZoneEntityItem::resetRenderingPropertiesChanged() { } void ZoneEntityItem::setHazeMode(const uint32_t value) { - if (value < COMPONENT_MODE_ITEM_COUNT) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _hazeMode) { _hazeMode = value; _hazePropertiesChanged = true; } @@ -373,7 +373,7 @@ uint32_t ZoneEntityItem::getHazeMode() const { } void ZoneEntityItem::setKeyLightMode(const uint32_t value) { - if (value < COMPONENT_MODE_ITEM_COUNT) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _keyLightMode) { _keyLightMode = value; _keyLightPropertiesChanged = true; } @@ -384,7 +384,7 @@ uint32_t ZoneEntityItem::getKeyLightMode() const { } void ZoneEntityItem::setAmbientLightMode(const uint32_t value) { - if (value < COMPONENT_MODE_ITEM_COUNT) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _ambientLightMode) { _ambientLightMode = value; _ambientLightPropertiesChanged = true; } @@ -395,7 +395,7 @@ uint32_t ZoneEntityItem::getAmbientLightMode() const { } void ZoneEntityItem::setSkyboxMode(const uint32_t value) { - if (value < COMPONENT_MODE_ITEM_COUNT) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _skyboxMode) { _skyboxMode = value; _skyboxPropertiesChanged = true; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 9bae9927e6..63fde0a2e9 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -202,7 +202,7 @@ enum class EntityVersion : PacketVersion { HazeEffect, StaticCertJsonVersionOne, OwnershipChallengeFix, - ZoneLightInheritModes + ZoneLightInheritModes }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/render-utils/src/LightPayload.h b/libraries/render-utils/src/LightPayload.h index 0cf8f8e2de..b55373c9a8 100644 --- a/libraries/render-utils/src/LightPayload.h +++ b/libraries/render-utils/src/LightPayload.h @@ -67,7 +67,6 @@ public: NetworkTexturePointer _ambientTexture; QString _ambientTextureURL; bool _pendingAmbientTexture { false }; - bool _validAmbientTextureURL { false }; protected: model::LightPointer _light; From 4f5727e6b0c143a400c6e0be063fd91eb6eb8c4a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 8 Jan 2018 15:25:16 -0800 Subject: [PATCH 22/31] add _needsRenderUpdate flag --- libraries/entities-renderer/src/RenderableEntityItem.cpp | 9 ++++++++- libraries/entities-renderer/src/RenderableEntityItem.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 1f11802405..fb9aba636b 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -141,7 +141,10 @@ std::shared_ptr make_renderer(const EntityItemPointer& entity) { } EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity) { - connect(entity.get(), &EntityItem::requestRenderUpdate, this, &EntityRenderer::requestRenderUpdate); + connect(entity.get(), &EntityItem::requestRenderUpdate, this, [&] { + _needsRenderUpdate = true; + emit requestRenderUpdate(); + }); } EntityRenderer::~EntityRenderer() { } @@ -312,6 +315,9 @@ void EntityRenderer::setSubRenderItemIDs(const render::ItemIDs& ids) { // Returns true if the item needs to have updateInscene called because of internal rendering // changes (animation, fading, etc) bool EntityRenderer::needsRenderUpdate() const { + if (_needsRenderUpdate) { + return true; + } if (_prevIsTransparent != isTransparent()) { return true; } @@ -360,6 +366,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); + _needsRenderUpdate = false; }); } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 5deb69711d..8eb82e2c6e 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -125,6 +125,7 @@ protected: bool _prevIsTransparent { false }; bool _visible { false }; bool _moving { false }; + bool _needsRenderUpdate { false }; // Only touched on the rendering thread bool _renderUpdateQueued{ false }; From c841ec6c8c7631fd55ea01dddf92e22bd5edc725 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 8 Jan 2018 18:15:20 -0800 Subject: [PATCH 23/31] Copy Skybox URL to Ambient URL if background mode is Skybox and Ambient URL is blank. --- libraries/entities/src/EntityTree.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 7e31e45bf0..ae243623f6 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2291,8 +2291,13 @@ bool EntityTree::readFromMap(QVariantMap& map) { if (entityMap.contains("backgroundMode") && (entityMap["backgroundMode"].toString() == "nothing")) { properties.setSkyboxMode(COMPONENT_MODE_INHERIT); } else { - // either the background mode field is missing (shouldn't happen) or the background mode is "skybox" + // Either the background mode field is missing (shouldn't happen) or the background mode is "skybox" properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + + // Copy the skybox URL if the ambient URL is empty, as this is the legacy behaviour + if (properties.getAmbientLight().getAmbientURL() == "") { + properties.getAmbientLight().setAmbientURL(properties.getSkybox().getURL()); + } } } From 360a7cc4c68aed249457f1e654ac93f367dc1986 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 9 Jan 2018 11:08:18 -0800 Subject: [PATCH 24/31] CR feedback --- interface/src/commerce/Ledger.cpp | 10 +++++----- interface/src/commerce/Ledger.h | 4 ++-- interface/src/commerce/QmlCommerce.cpp | 2 +- interface/src/commerce/QmlCommerce.h | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 5d765e2c32..d2ce28d2ea 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -72,7 +72,7 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } -void Ledger::keysQuery(const QString& endpoint, QJsonObject& requestParams, const QString& success, const QString& fail) { +void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& requestParams) { auto wallet = DependencyManager::get(); requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); @@ -104,11 +104,11 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) { } void Ledger::balance(const QStringList& keys) { - keysQuery("balance", QJsonObject(), "balanceSuccess", "balanceFailure"); + keysQuery("balance", "balanceSuccess", "balanceFailure"); } void Ledger::inventory(const QStringList& keys) { - keysQuery("inventory", QJsonObject(), "inventorySuccess", "inventoryFailure"); + keysQuery("inventory", "inventorySuccess", "inventoryFailure"); } QString amountString(const QString& label, const QString&color, const QJsonValue& moneyValue, const QJsonValue& certsValue) { @@ -177,11 +177,11 @@ void Ledger::historyFailure(QNetworkReply& reply) { failResponse("history", reply); } -void Ledger::history(const QStringList& keys, const QString& pageNumber) { +void Ledger::history(const QStringList& keys, const int& pageNumber) { QJsonObject params; params["per_page"] = 100; params["page"] = pageNumber; - keysQuery("history", params, "historySuccess", "historyFailure"); + keysQuery("history", "historySuccess", "historyFailure", params); } // The api/failResponse is called just for the side effect of logging. diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index db3d1f064a..95329842cc 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -29,7 +29,7 @@ public: bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); void inventory(const QStringList& keys); - void history(const QStringList& keys, const QString& pageNumber); + void history(const QStringList& keys, const int& pageNumber); void account(); void reset(); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); @@ -79,7 +79,7 @@ private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); QJsonObject failResponse(const QString& label, QNetworkReply& reply); void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); - void keysQuery(const QString& endpoint, QJsonObject& extraRequestParams, const QString& success, const QString& fail); + void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams = QJsonObject()); void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false); }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 36608fe0a6..320c7e041c 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -96,7 +96,7 @@ void QmlCommerce::inventory() { } } -void QmlCommerce::history(const QString& pageNumber) { +void QmlCommerce::history(const int& pageNumber) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 88223aacb0..f2e6c82021 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -60,7 +60,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); - Q_INVOKABLE void history(const QString& pageNumber); + Q_INVOKABLE void history(const int& pageNumber); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); Q_INVOKABLE void resetLocalWalletOnly(); From d31dec7acb5e1d81f1662a762bbd8794a1be876a Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 9 Jan 2018 12:41:56 -0800 Subject: [PATCH 25/31] Fix unix build errors --- interface/src/commerce/Ledger.cpp | 8 ++++++++ interface/src/commerce/Ledger.h | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index d2ce28d2ea..ba153c383d 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -79,6 +79,14 @@ void Ledger::keysQuery(const QString& endpoint, const QString& success, const QS send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams); } +void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { + auto wallet = DependencyManager::get(); + QJsonObject requestParams; + requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + + send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams); +} + void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) { QJsonObject transaction; transaction["hfc_key"] = hfc_key; diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 95329842cc..5d90aa0808 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -79,7 +79,8 @@ private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); QJsonObject failResponse(const QString& label, QNetworkReply& reply); void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); - void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams = QJsonObject()); + void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams); + void keysQuery(const QString& endpoint, const QString& success, const QString& fail); void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false); }; From 7c56a9ffb2c1fd842ce590bf11d9bc912d730603 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 9 Jan 2018 12:43:43 -0800 Subject: [PATCH 26/31] model meta is only withTypeShape if visual geometry request fails --- interface/src/Application.cpp | 3 ++ interface/src/Menu.cpp | 2 +- .../src/EntityTreeRenderer.cpp | 1 + .../src/EntityTreeRenderer.h | 6 +++ .../src/RenderableModelEntityItem.cpp | 49 ++++++++++--------- .../src/RenderableModelEntityItem.h | 12 +++-- 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f3c41565f8..73f664ef61 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1851,6 +1851,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) { DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); + EntityTreeRenderer::setRenderDebugHullsOperator([] { + return Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls); + }); // Preload Tablet sounds DependencyManager::get()->preloadSounds(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d295e96867..424db37317 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -698,7 +698,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned, 0, false, drawStatusConfig, SLOT(setShowNetwork(bool))); } - addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls, 0, false, qApp->getEntities().data(), SIGNAL(setRenderDebugHulls())); // Developer > Ask to Reset Settings addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AskToResetSettings, 0, false); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 3a737e581b..c2a201a965 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -42,6 +42,7 @@ size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; +std::function EntityTreeRenderer::_renderDebugHullsOperator = [] { return false; }; EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, AbstractScriptingServicesInterface* scriptingServices) : diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index dccd14aac6..f5cedfdd01 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -116,10 +116,14 @@ public: EntityItemPointer getEntity(const EntityItemID& id); void onEntityChanged(const EntityItemID& id); + static void setRenderDebugHullsOperator(std::function renderDebugHullsOperator) { _renderDebugHullsOperator = renderDebugHullsOperator; } + static bool shouldRenderDebugHulls() { return _renderDebugHullsOperator(); } + signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); + void setRenderDebugHulls(); public slots: void addingEntity(const EntityItemID& entityID); @@ -255,6 +259,8 @@ private: static int _entitiesScriptEngineCount; static CalculateEntityLoadingPriority _calculateEntityLoadingPriorityFunc; static std::function _entitiesShouldFadeFunction; + + static std::function _renderDebugHullsOperator; }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c756070bc3..676f3a1ccd 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -954,8 +954,23 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { using namespace render; using namespace render::entities; +ModelEntityRenderer::ModelEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { + connect(DependencyManager::get().data(), &EntityTreeRenderer::setRenderDebugHulls, this, [&] { + _needsCollisionGeometryUpdate = true; + emit requestRenderUpdate(); + }); +} + +void ModelEntityRenderer::setKey(bool didVisualGeometryRequestSucceed) { + if (didVisualGeometryRequestSucceed) { + _itemKey = ItemKey::Builder().withTypeMeta(); + } else { + _itemKey = ItemKey::Builder().withTypeMeta().withTypeShape(); + } +} + ItemKey ModelEntityRenderer::getKey() { - return ItemKey::Builder().withTypeMeta().withTypeShape(); + return _itemKey; } uint32_t ModelEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) { @@ -1209,7 +1224,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // Check for addition if (_hasModel && !(bool)_model) { model = std::make_shared(nullptr, entity.get()); - connect(model.get(), &Model::setURLFinished, this, &ModelEntityRenderer::requestRenderUpdate); + connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) { + setKey(didVisualGeometryRequestSucceed); + emit requestRenderUpdate(); + }); connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); connect(entity.get(), &RenderableModelEntityItem::requestCollisionGeometryUpdate, this, &ModelEntityRenderer::flagForCollisionGeometryUpdate); model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); @@ -1275,7 +1293,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce setCollisionMeshKey(entity->getCollisionMeshKey()); _needsCollisionGeometryUpdate = false; ShapeType type = entity->getShapeType(); - if (_showCollisionGeometry && type != SHAPE_TYPE_STATIC_MESH && type != SHAPE_TYPE_NONE) { + if (DependencyManager::get()->shouldRenderDebugHulls() && type != SHAPE_TYPE_STATIC_MESH && type != SHAPE_TYPE_NONE) { // NOTE: it is OK if _collisionMeshKey is nullptr model::MeshPointer mesh = collisionMeshCache.getMesh(_collisionMeshKey); // NOTE: the model will render the collisionGeometry if it has one @@ -1342,20 +1360,11 @@ void ModelEntityRenderer::doRender(RenderArgs* args) { DETAILED_PROFILE_RANGE(render_detail, "MetaModelRender"); DETAILED_PERFORMANCE_TIMER("RMEIrender"); - ModelPointer model; - withReadLock([&]{ - model = _model; - }); - - // If we don't have a model, or the model doesn't have visual geometry, render our bounding box as green wireframe - if (!model || (model && model->didVisualGeometryRequestFail())) { - static glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); - gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getModelTransform()); // we want to include the scale as well - DependencyManager::get()->renderWireCubeInstance(args, batch, greenColor); - return; - } - + // If the model doesn't have visual geometry, render our bounding box as green wireframe + static glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); + gpu::Batch& batch = *args->_batch; + batch.setModelTransform(getModelTransform()); // we want to include the scale as well + DependencyManager::get()->renderWireCubeInstance(args, batch, greenColor); // Enqueue updates for the next frame #if WANT_EXTRA_DEBUGGING @@ -1366,12 +1375,6 @@ void ModelEntityRenderer::doRender(RenderArgs* args) { // Remap textures for the next frame to avoid flicker // remapTextures(); - - bool showCollisionGeometry = (bool)(args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS); - if (showCollisionGeometry != _showCollisionGeometry) { - _showCollisionGeometry = showCollisionGeometry; - flagForCollisionGeometryUpdate(); - } } void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const QStringList& modelJointNames) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index b7a339e1e1..1f3c4af054 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -133,12 +133,13 @@ class ModelEntityRenderer : public TypedEntityRenderer Date: Tue, 9 Jan 2018 12:59:32 -0800 Subject: [PATCH 27/31] provide more context for update perf stats --- interface/src/Application.cpp | 33 ++++++++++++++++++++++----------- interface/src/LODManager.cpp | 2 +- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f3c41565f8..a20c9e3160 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5004,8 +5004,6 @@ void Application::update(float deltaTime) { } } - PerformanceTimer perfTimer("misc"); - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); @@ -5014,11 +5012,13 @@ void Application::update(float deltaTime) { // TODO: break these out into distinct perfTimers when they prove interesting { PROFILE_RANGE(app, "PickManager"); + PerformanceTimer perfTimer("pickManager"); DependencyManager::get()->update(); } { PROFILE_RANGE(app, "PointerManager"); + PerformanceTimer perfTimer("pointerManager"); DependencyManager::get()->update(); } @@ -5044,8 +5044,8 @@ void Application::update(float deltaTime) { // Update my voxel servers with my current voxel query... { PROFILE_RANGE_EX(app, "QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); - QMutexLocker viewLocker(&_viewMutex); PerformanceTimer perfTimer("queryOctree"); + QMutexLocker viewLocker(&_viewMutex); quint64 sinceLastQuery = now - _lastQueriedTime; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; @@ -5081,12 +5081,14 @@ void Application::update(float deltaTime) { } } - avatarManager->postUpdate(deltaTime, getMain3DScene()); - + { + PerformanceTimer perfTimer("avatarManager/postUpdate"); + avatarManager->postUpdate(deltaTime, getMain3DScene()); + } { - PROFILE_RANGE_EX(app, "PreRenderLambdas", 0xffff0000, (uint64_t)0); - + PROFILE_RANGE_EX(app, "PostUpdateLambdas", 0xffff0000, (uint64_t)0); + PerformanceTimer perfTimer("postUpdateLambdas"); std::unique_lock guard(_postUpdateLambdasLock); for (auto& iter : _postUpdateLambdas) { iter.second(); @@ -5095,6 +5097,7 @@ void Application::update(float deltaTime) { } editRenderArgs([this](AppRenderArgs& appRenderArgs) { + PerformanceTimer perfTimer("editRenderArgs"); appRenderArgs._headPose= getHMDSensorPose(); auto myAvatar = getMyAvatar(); @@ -5208,12 +5211,20 @@ void Application::update(float deltaTime) { } }); - AnimDebugDraw::getInstance().update(); + { + PerformanceTimer perfTimer("limitless"); + AnimDebugDraw::getInstance().update(); + } - DependencyManager::get()->update(); + { + PerformanceTimer perfTimer("limitless"); + DependencyManager::get()->update(); + } - // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over - getMain3DScene()->enqueueFrame(); + { // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over + PerformanceTimer perfTimer("enqueueFrame"); + getMain3DScene()->enqueueFrame(); + } } void Application::sendAvatarViewFrustum() { diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 9e6fabd439..087a93cffe 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -50,7 +50,7 @@ float LODManager::getLODIncreaseFPS() { const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.1f; // sec // // Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its -// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage settle +// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage to settle // to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few // multiples of the running average timescale: const uint64_t LOD_AUTO_ADJUST_PERIOD = 5 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec From c3f6faed00559763fe26c4ca7954468fd6078ffe Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 9 Jan 2018 13:27:57 -0800 Subject: [PATCH 28/31] Deal correctly with inheritance at top-most level. --- .../src/DeferredLightingEffect.cpp | 3 +- libraries/render-utils/src/LightStage.cpp | 46 ++++++++++++------- libraries/render-utils/src/LightStage.h | 25 ++++------ 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 151fb7990d..81a33f17e3 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -763,7 +763,8 @@ void DefaultLightingSetup::run(const RenderContextPointer& renderContext) { _defaultLight = lp; // Add the global light to the light stage (for later shadow rendering) - _defaultLightID = lightStage->addLight(lp); + // Set this light to be the default + _defaultLightID = lightStage->addLight(lp, true); lightStage->addShadow(_defaultLightID); } diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index 0cc30495b8..d62f52047b 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -34,28 +34,31 @@ LightStage::LightStage() { ambientOffLight->setColor(model::Vec3(0.0)); ambientOffLight->setIntensity(0.0f); ambientOffLight->setType(model::Light::Type::AMBIENT); - _ambientOffLight = addLight(ambientOffLight); + _ambientOffLightId = addLight(ambientOffLight); const LightPointer pointOffLight { std::make_shared() }; pointOffLight->setAmbientIntensity(0.0f); pointOffLight->setColor(model::Vec3(0.0)); pointOffLight->setIntensity(0.0f); pointOffLight->setType(model::Light::Type::POINT); - _pointOffLight = addLight(pointOffLight); + _pointOffLightId = addLight(pointOffLight); const LightPointer spotOffLight { std::make_shared() }; spotOffLight->setAmbientIntensity(0.0f); spotOffLight->setColor(model::Vec3(0.0)); spotOffLight->setIntensity(0.0f); spotOffLight->setType(model::Light::Type::SPOT); - _spotOffLight = addLight(spotOffLight); + _spotOffLightId = addLight(spotOffLight); const LightPointer sunOffLight { std::make_shared() }; sunOffLight->setAmbientIntensity(0.0f); sunOffLight->setColor(model::Vec3(0.0)); sunOffLight->setIntensity(0.0f); sunOffLight->setType(model::Light::Type::SUN); - _sunOffLight = addLight(sunOffLight); + _sunOffLightId = addLight(sunOffLight); + + // Set default light to the off ambient light (until changed) + _defaultLightId = _ambientOffLightId; } LightStage::Shadow::Schema::Schema() { @@ -294,10 +297,12 @@ LightStage::Index LightStage::findLight(const LightPointer& light) const { } } -LightStage::Index LightStage::addLight(const LightPointer& light) { +LightStage::Index LightStage::addLight(const LightPointer& light, const bool shouldSetAsDefault) { + Index lightId; + auto found = _lightMap.find(light); if (found == _lightMap.end()) { - auto lightId = _lights.newElement(light); + lightId = _lights.newElement(light); // Avoid failing to allocate a light, just pass if (lightId != INVALID_INDEX) { @@ -314,10 +319,15 @@ LightStage::Index LightStage::addLight(const LightPointer& light) { updateLightArrayBuffer(lightId); } - return lightId; } else { - return (*found).second; + lightId = (*found).second; } + + if (shouldSetAsDefault) { + _defaultLightId = lightId; + } + + return lightId; } LightStage::Index LightStage::addShadow(Index lightIndex, float maxDistance, unsigned int cascadeCount) { @@ -349,24 +359,27 @@ LightStage::LightPointer LightStage::removeLight(Index index) { } LightStage::LightPointer LightStage::getCurrentKeyLight() const { - Index keyLightId{ 0 }; - if (!_currentFrame._sunLights.empty()) { + Index keyLightId{ _defaultLightId }; + // There is always at least 1 light (the off light) + if (_currentFrame._sunLights.size() > 1) { keyLightId = _currentFrame._sunLights.front(); } return _lights.get(keyLightId); } LightStage::LightPointer LightStage::getCurrentAmbientLight() const { - Index keyLightId{ 0 }; - if (!_currentFrame._ambientLights.empty()) { + Index keyLightId { _defaultLightId }; + // There is always at least 1 light (the off light) + if (_currentFrame._ambientLights.size() > 1) { keyLightId = _currentFrame._ambientLights.front(); } return _lights.get(keyLightId); } LightStage::ShadowPointer LightStage::getCurrentKeyShadow() const { - Index keyLightId{ 0 }; - if (!_currentFrame._sunLights.empty()) { + Index keyLightId { _defaultLightId }; + // There is always at least 1 light (the off light) + if (_currentFrame._sunLights.size() > 1) { keyLightId = _currentFrame._sunLights.front(); } auto shadow = getShadow(keyLightId); @@ -375,8 +388,9 @@ LightStage::ShadowPointer LightStage::getCurrentKeyShadow() const { } LightStage::LightAndShadow LightStage::getCurrentKeyLightAndShadow() const { - Index keyLightId{ 0 }; - if (!_currentFrame._sunLights.empty()) { + Index keyLightId { _defaultLightId }; + // There is always at least 1 light (the off light) + if (_currentFrame._sunLights.size() > 1) { keyLightId = _currentFrame._sunLights.front(); } auto shadow = getShadow(keyLightId); diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 5ba0b02131..b47e454b88 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -118,7 +118,7 @@ public: using Shadows = render::indexed_container::IndexedPointerVector; Index findLight(const LightPointer& light) const; - Index addLight(const LightPointer& light); + Index addLight(const LightPointer& light, const bool shouldSetAsDefault = false); Index addShadow(Index lightIndex, float maxDistance = 20.0f, unsigned int cascadeCount = 1U); @@ -185,10 +185,10 @@ public: Frame _currentFrame; - Index getAmbientOffLight() { return _ambientOffLight; } - Index getPointOffLight() { return _pointOffLight; } - Index getSpotOffLight() { return _spotOffLight; } - Index getSunOffLight() { return _sunOffLight; } + Index getAmbientOffLight() { return _ambientOffLightId; } + Index getPointOffLight() { return _pointOffLightId; } + Index getSpotOffLight() { return _spotOffLightId; } + Index getSunOffLight() { return _sunOffLightId; } protected: @@ -205,17 +205,12 @@ protected: LightMap _lightMap; // define off lights + Index _ambientOffLightId; + Index _pointOffLightId; + Index _spotOffLightId; + Index _sunOffLightId; - const LightPointer ambientOffLight { std::make_shared() }; - const LightPointer pointOffLight { std::make_shared() }; - const LightPointer spotOffLight { std::make_shared() }; - const LightPointer sunOffLight { std::make_shared() }; - - Index _ambientOffLight; - Index _pointOffLight; - Index _spotOffLight; - Index _sunOffLight; - + Index _defaultLightId; }; using LightStagePointer = std::shared_ptr; From edf25c3e4e319b3c05742edd4d04acbd667727f7 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 9 Jan 2018 14:14:54 -0800 Subject: [PATCH 29/31] Remove code duplication... --- interface/src/commerce/Ledger.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index ba153c383d..51658ddef8 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -80,11 +80,8 @@ void Ledger::keysQuery(const QString& endpoint, const QString& success, const QS } void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { - auto wallet = DependencyManager::get(); QJsonObject requestParams; - requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); - - send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams); + keysQuery(endpoint, success, fail, requestParams); } void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) { From b53e411184aa17307b6065d1ee4e825eadbe13a7 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 9 Jan 2018 15:24:46 -0800 Subject: [PATCH 30/31] Corrected pushing of default lights. --- libraries/render-utils/src/LightStage.cpp | 12 ++++-------- libraries/render-utils/src/LightStage.h | 2 ++ libraries/render-utils/src/ZoneRenderer.cpp | 7 ++++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index d62f52047b..42b161ba74 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -360,8 +360,7 @@ LightStage::LightPointer LightStage::removeLight(Index index) { LightStage::LightPointer LightStage::getCurrentKeyLight() const { Index keyLightId{ _defaultLightId }; - // There is always at least 1 light (the off light) - if (_currentFrame._sunLights.size() > 1) { + if (!_currentFrame._sunLights.empty()) { keyLightId = _currentFrame._sunLights.front(); } return _lights.get(keyLightId); @@ -369,8 +368,7 @@ LightStage::LightPointer LightStage::getCurrentKeyLight() const { LightStage::LightPointer LightStage::getCurrentAmbientLight() const { Index keyLightId { _defaultLightId }; - // There is always at least 1 light (the off light) - if (_currentFrame._ambientLights.size() > 1) { + if (!_currentFrame._ambientLights.empty()) { keyLightId = _currentFrame._ambientLights.front(); } return _lights.get(keyLightId); @@ -378,8 +376,7 @@ LightStage::LightPointer LightStage::getCurrentAmbientLight() const { LightStage::ShadowPointer LightStage::getCurrentKeyShadow() const { Index keyLightId { _defaultLightId }; - // There is always at least 1 light (the off light) - if (_currentFrame._sunLights.size() > 1) { + if (!_currentFrame._sunLights.empty()) { keyLightId = _currentFrame._sunLights.front(); } auto shadow = getShadow(keyLightId); @@ -389,8 +386,7 @@ LightStage::ShadowPointer LightStage::getCurrentKeyShadow() const { LightStage::LightAndShadow LightStage::getCurrentKeyLightAndShadow() const { Index keyLightId { _defaultLightId }; - // There is always at least 1 light (the off light) - if (_currentFrame._sunLights.size() > 1) { + if (!_currentFrame._sunLights.empty()) { keyLightId = _currentFrame._sunLights.front(); } auto shadow = getShadow(keyLightId); diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index b47e454b88..8da2ba3900 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -119,6 +119,8 @@ public: Index findLight(const LightPointer& light) const; Index addLight(const LightPointer& light, const bool shouldSetAsDefault = false); + + const Index getDefaultLight() const { return _defaultLightId; } Index addShadow(Index lightIndex, float maxDistance = 20.0f, unsigned int cascadeCount = 1U); diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 8494688f92..c0d01c2eaf 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -68,10 +68,11 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) auto lightStage = context->_scene->getStage(); assert(lightStage); - lightStage->_currentFrame.pushSunLight(0); - lightStage->_currentFrame.pushAmbientLight(0); - hazeStage->_currentFrame.pushHaze(0); + lightStage->_currentFrame.pushSunLight(lightStage->getDefaultLight()); + lightStage->_currentFrame.pushAmbientLight(lightStage->getDefaultLight()); + backgroundStage->_currentFrame.pushBackground(0); + hazeStage->_currentFrame.pushHaze(0); } const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { From 5b1f8e83d4c1bf0bad1d7efc5edb636979f07360 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 9 Jan 2018 15:46:15 -0800 Subject: [PATCH 31/31] Removed gcc warning. --- libraries/render-utils/src/LightStage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 8da2ba3900..3dcae550f7 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -120,7 +120,7 @@ public: Index findLight(const LightPointer& light) const; Index addLight(const LightPointer& light, const bool shouldSetAsDefault = false); - const Index getDefaultLight() const { return _defaultLightId; } + Index getDefaultLight() { return _defaultLightId; } Index addShadow(Index lightIndex, float maxDistance = 20.0f, unsigned int cascadeCount = 1U);