From ca3777442c0c16ef4c16a08f01e3eaaf698c1248 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 11 Dec 2017 16:54:35 -0800 Subject: [PATCH 01/90] Added visualization of the differences between 2 images. --- tools/auto-tester/src/ImageComparer.cpp | 6 +- tools/auto-tester/src/common.h | 5 ++ tools/auto-tester/src/ui/MismatchWindow.cpp | 46 ++++++++++++- tools/auto-tester/src/ui/MismatchWindow.h | 2 + tools/auto-tester/src/ui/MismatchWindow.ui | 73 ++++++++++++--------- 5 files changed, 94 insertions(+), 38 deletions(-) diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index 121c98e16e..a80978e564 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -8,6 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "ImageComparer.h" +#include "common.h" #include @@ -26,11 +27,6 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co const double c1 = pow((K1 * L), 2); const double c2 = pow((K2 * L), 2); - // Coefficients for luminosity calculation - const double R_Y = 0.212655f; - const double G_Y = 0.715158f; - const double B_Y = 0.072187f; - // First go over all full 8x8 blocks // This is done in 3 loops // 1) Read the pixels into a linear array (an optimization) diff --git a/tools/auto-tester/src/common.h b/tools/auto-tester/src/common.h index 126177358f..0c21d79b33 100644 --- a/tools/auto-tester/src/common.h +++ b/tools/auto-tester/src/common.h @@ -34,4 +34,9 @@ enum UserResponse { USER_RESPONSE_ABORT }; +// Coefficients for luminosity calculation +const double R_Y = 0.212655f; +const double G_Y = 0.715158f; +const double B_Y = 0.072187f; + #endif // hifi_common_h diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index 07664a1667..ec6dd9ac82 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -16,6 +16,37 @@ MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { expectedImage->setScaledContents(true); resultImage->setScaledContents(true); + diffImage->setScaledContents(true); +} + +QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) { + // This is an optimization, as QImage.setPixel() is embarrassingly slow + unsigned char* buffer = new unsigned char[expectedImage.height() * expectedImage.width() * 3]; + + // loop over each pixel + for (int y = 0; y < expectedImage.height(); ++y) { + for (int x = 0; x < expectedImage.width(); ++x) { + QRgb pixelP = expectedImage.pixel(QPoint(x, y)); + QRgb pixelQ = resultImage.pixel(QPoint(x, y)); + + // Convert to luminance + double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); + double q = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); + + int absDiff = (int)(fabs(p - q)); + + buffer[3 * (x + y * expectedImage.width()) + 0] = absDiff; + buffer[3 * (x + y * expectedImage.width()) + 1] = absDiff; + buffer[3 * (x + y * expectedImage.width()) + 2] = absDiff; + } + } + + QImage diffImage(buffer, expectedImage.width(), expectedImage.height(), QImage::Format_RGB888); + QPixmap resultPixmap = QPixmap::fromImage(diffImage); + + delete[] buffer; + + return resultPixmap; } void MismatchWindow::setTestFailure(TestFailure testFailure) { @@ -24,10 +55,19 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) { imagePath->setText("Path to test: " + testFailure._pathname); expectedFilename->setText(testFailure._expectedImageFilename); - expectedImage->setPixmap(QPixmap(testFailure._pathname + testFailure._expectedImageFilename)); - resultFilename->setText(testFailure._actualImageFilename); - resultImage->setPixmap(QPixmap(testFailure._pathname + testFailure._actualImageFilename)); + + QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename); + QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename); + + QPixmap diffPixmap = computeDiffPixmap( + QImage(testFailure._pathname + testFailure._expectedImageFilename), + QImage(testFailure._pathname + testFailure._actualImageFilename) + ); + + expectedImage->setPixmap(expectedPixmap); + resultImage->setPixmap(actualPixmap); + diffImage->setPixmap(diffPixmap); } void MismatchWindow::on_passTestButton_clicked() { diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h index 7c72b7b0b7..af18832f2a 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.h +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -25,6 +25,8 @@ public: UserResponse getUserResponse() { return _userResponse; } + QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); + private slots: void on_passTestButton_clicked(); void on_failTestButton_clicked(); diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui index cab6c61e1c..090121c277 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.ui +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -6,8 +6,8 @@ 0 0 - 1585 - 694 + 1782 + 942 @@ -16,10 +16,10 @@ - 20 - 170 - 720 - 362 + 10 + 20 + 800 + 450 @@ -29,28 +29,41 @@ - 760 - 170 - 720 - 362 + 900 + 20 + 800 + 450 result image + + + + 540 + 480 + 800 + 450 + + + + diff image + + - 760 - 90 - 800 + 60 + 660 + 480 28 - 16 + 12 @@ -60,15 +73,15 @@ - 40 - 90 - 700 + 60 + 630 + 480 28 - 16 + 12 @@ -78,15 +91,15 @@ - 40 - 30 + 20 + 600 1200 28 - 16 + 12 @@ -97,7 +110,7 @@ 30 - 600 + 790 75 23 @@ -109,8 +122,8 @@ - 330 - 600 + 120 + 790 75 23 @@ -122,8 +135,8 @@ - 630 - 600 + 210 + 790 75 23 @@ -135,15 +148,15 @@ - 810 - 600 - 720 + 30 + 850 + 500 28 - 16 + 12 From 37deb2674aa63d9750168e56eb3e78dfa8a23c67 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 13 Dec 2017 17:18:05 +0100 Subject: [PATCH 02/90] 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 03/90] 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 04/90] 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 05/90] 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 05b42726690a9bd0b91046dee2b79d167881b9ff Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 14 Dec 2017 11:40:30 -0800 Subject: [PATCH 06/90] WIP - writing test results to disk. --- tools/auto-tester/src/Test.cpp | 33 ++++++++++++++++++++++++++++----- tools/auto-tester/src/Test.h | 6 +++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 8cb36fcfca..d9269e3391 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -21,7 +21,20 @@ Test::Test() { mismatchWindow.setModal(true); } -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages) { +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory) { + // Delete any previous test results, if user agrees + QString s = testDirectory + "/" + testResultsFolder; + QFileInfo fileInfo(testDirectory + "/" + testResultsFolder); + while (fileInfo.exists()) { + messageBox.setText("Previous test results have been found"); + messageBox.setInformativeText("Delete " + testResultsFolder + " before continuing"); + messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + int reply = messageBox.exec(); + if (reply == QMessageBox::Cancel) { + return false; + } + } + // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. const double THRESHOLD{ 0.999 }; @@ -45,12 +58,14 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage } if (similarityIndex < THRESHOLD) { - mismatchWindow.setTestFailure(TestFailure{ + TestFailure testFailure = TestFailure{ (float)similarityIndex, expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /) QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image - }); + }; + + mismatchWindow.setTestFailure(testFailure); mismatchWindow.exec(); @@ -58,6 +73,7 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage case USER_RESPONSE_PASS: break; case USE_RESPONSE_FAIL: + appendTestResultsToFile(testDirectory, testFailure); success = false; break; case USER_RESPONSE_ABORT: @@ -74,6 +90,13 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage return success; } +void Test::appendTestResultsToFile(QString testDirectory, TestFailure testFailure) { + QFileInfo fileInfo(testResultsFileName); + if (!fileInfo.exists()) { + } + +} + void Test::evaluateTests() { // Get list of JPEG images in folder, sorted by name QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); @@ -107,7 +130,7 @@ void Test::evaluateTests() { exit(-1); } - bool success = compareImageLists(expectedImages, resultImages); + bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory); if (success) { messageBox.information(0, "Success", "All images are as expected"); @@ -164,7 +187,7 @@ void Test::evaluateTestsRecursively() { } // Set success to false if any test has failed - success &= compareImageLists(expectedImages, resultImages); + success &= compareImageLists(expectedImages, resultImages, directory); } if (success) { diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 1f7b1e92a7..aa71aa5ba4 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -35,8 +35,12 @@ public: void importTest(QTextStream& textStream, const QString& testPathname, int testNumber); + void appendTestResultsToFile(QString testDirectory, TestFailure testFailure); + private: const QString testFilename{ "test.js" }; + const QString testResultsFolder{ "TestResults" }; + const QString testResultsFileName{ "TestResults.txt" }; QMessageBox messageBox; @@ -49,7 +53,7 @@ private: ImageComparer imageComparer; - bool compareImageLists(QStringList expectedImages, QStringList resultImages); + bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory); }; #endif // hifi_test_h From 2a325d45e0ebb64a046c95371f1357b77d060464 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 15 Dec 2017 09:21:36 +0100 Subject: [PATCH 07/90] 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 08/90] 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 418d741b39b6a61ca9a7cfb6fde73c38ab53ada4 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 15 Dec 2017 11:55:46 -0800 Subject: [PATCH 09/90] Auto-tester now saves results in a folder hierarchy. --- tools/auto-tester/src/Test.cpp | 65 ++++++++++++++++++--- tools/auto-tester/src/Test.h | 2 +- tools/auto-tester/src/ui/MismatchWindow.cpp | 20 +++++-- tools/auto-tester/src/ui/MismatchWindow.h | 3 + tools/auto-tester/src/ui/MismatchWindow.ui | 30 +++++++++- 5 files changed, 102 insertions(+), 18 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index d9269e3391..25477b3b91 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -22,10 +22,10 @@ Test::Test() { } bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory) { - // Delete any previous test results, if user agrees - QString s = testDirectory + "/" + testResultsFolder; - QFileInfo fileInfo(testDirectory + "/" + testResultsFolder); - while (fileInfo.exists()) { + // If a previous test results folder is found then wait for the user to delete it, or cancel + // (e.g. the user may want to move the folder elsewhere) + QString testResultsFolderPath { testDirectory + "/" + testResultsFolder }; + while (QDir().exists(testResultsFolderPath)) { messageBox.setText("Previous test results have been found"); messageBox.setInformativeText("Delete " + testResultsFolder + " before continuing"); messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); @@ -35,9 +35,12 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage } } + // Create a new test results folder + QDir().mkdir(testResultsFolderPath); + // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. - const double THRESHOLD{ 0.999 }; + const double THRESHOLD { 0.999 }; bool success{ true }; bool keepOn{ true }; for (int i = 0; keepOn && i < expectedImages.length(); ++i) { @@ -73,7 +76,7 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage case USER_RESPONSE_PASS: break; case USE_RESPONSE_FAIL: - appendTestResultsToFile(testDirectory, testFailure); + appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); success = false; break; case USER_RESPONSE_ABORT: @@ -90,11 +93,55 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage return success; } -void Test::appendTestResultsToFile(QString testDirectory, TestFailure testFailure) { - QFileInfo fileInfo(testResultsFileName); - if (!fileInfo.exists()) { +void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { + if (!QDir().exists(testResultsFolderPath)) { + messageBox.critical(0, "Internal error", "Folder " + testResultsFolderPath + " not found"); + exit(-1); } + static int index = 1; + QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; + if (!QDir().mkdir(failureFolderPath)) { + messageBox.critical(0, "Internal error", "Failed to create folder " + failureFolderPath); + exit(-1); + } + ++index; + + QString descriptionFileName { "ReadMe.txt" }; + QFile descriptionFile(failureFolderPath + "/" +descriptionFileName); + if (!descriptionFile.open(QIODevice::ReadWrite)) { + messageBox.critical(0, "Internal error", "Failed to create file " + descriptionFileName); + exit(-1); + } + + // Create text file describing the failure + QTextStream stream(&descriptionFile); + stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/' + stream << "Expected image was " << testFailure._expectedImageFilename << endl; + stream << "Actual image was " << testFailure._actualImageFilename << endl; + stream << "Similarity index was " << testFailure._error << endl; + + descriptionFile.close(); + + // Copy expected and actual images, and save the difference image + QString sourceFile; + QString destinationFile; + + sourceFile = testFailure._pathname + testFailure._expectedImageFilename; + destinationFile = failureFolderPath + "/" + "Expected Image.jpg"; + if (!QFile::copy(sourceFile, destinationFile)) { + messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + + sourceFile = testFailure._pathname + testFailure._actualImageFilename; + destinationFile = failureFolderPath + "/" + "Actual Image.jpg"; + if (!QFile::copy(sourceFile, destinationFile)) { + messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + + comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg"); } void Test::evaluateTests() { diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index aa71aa5ba4..73814c2311 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -35,7 +35,7 @@ public: void importTest(QTextStream& textStream, const QString& testPathname, int testNumber); - void appendTestResultsToFile(QString testDirectory, TestFailure testFailure); + void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); private: const QString testFilename{ "test.js" }; diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index ec6dd9ac82..711ead4fbe 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -33,11 +33,15 @@ QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultIma double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); double q = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); - int absDiff = (int)(fabs(p - q)); - - buffer[3 * (x + y * expectedImage.width()) + 0] = absDiff; - buffer[3 * (x + y * expectedImage.width()) + 1] = absDiff; - buffer[3 * (x + y * expectedImage.width()) + 2] = absDiff; + // The intensity value is modified to increase the brightness of the displayed image + double absoluteDifference = fabs(p - q) / 255.0; + double modifiedDifference = pow(absoluteDifference, 0.5); + + int difference = (int)(modifiedDifference * 255.0); + + buffer[3 * (x + y * expectedImage.width()) + 0] = difference; + buffer[3 * (x + y * expectedImage.width()) + 1] = difference; + buffer[3 * (x + y * expectedImage.width()) + 2] = difference; } } @@ -60,7 +64,7 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) { QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename); QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename); - QPixmap diffPixmap = computeDiffPixmap( + diffPixmap = computeDiffPixmap( QImage(testFailure._pathname + testFailure._expectedImageFilename), QImage(testFailure._pathname + testFailure._actualImageFilename) ); @@ -84,3 +88,7 @@ void MismatchWindow::on_abortTestsButton_clicked() { _userResponse = USER_RESPONSE_ABORT; close(); } + +QPixmap MismatchWindow::getComparisonImage() { + return diffPixmap; +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h index af18832f2a..ad8be16580 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.h +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -26,6 +26,7 @@ public: UserResponse getUserResponse() { return _userResponse; } QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); + QPixmap getComparisonImage(); private slots: void on_passTestButton_clicked(); @@ -34,6 +35,8 @@ private slots: private: UserResponse _userResponse{ USER_RESPONSE_INVALID }; + + QPixmap diffPixmap; }; diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui index 090121c277..392bc1774b 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.ui +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -17,7 +17,7 @@ 10 - 20 + 25 800 450 @@ -30,7 +30,7 @@ 900 - 20 + 25 800 450 @@ -163,6 +163,32 @@ similarity + + + + 30 + 5 + 151 + 16 + + + + Expected Image + + + + + + 930 + 5 + 151 + 16 + + + + Actual Image + + From c7a45ec988fc361006db002444a5c94f96e1ddbc Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 15 Dec 2017 12:52:13 -0800 Subject: [PATCH 10/90] Completed storing test results. --- libraries/gl/src/gl/GLShaders.cpp | 29 +++++++------- tools/auto-tester/src/Test.cpp | 19 +++++---- tools/auto-tester/src/Test.h | 6 +-- tools/auto-tester/src/ui/AutoTester.cpp | 6 ++- tools/auto-tester/src/ui/AutoTester.h | 6 +-- tools/auto-tester/src/ui/AutoTester.ui | 52 ++++++++++++++++--------- 6 files changed, 72 insertions(+), 46 deletions(-) diff --git a/libraries/gl/src/gl/GLShaders.cpp b/libraries/gl/src/gl/GLShaders.cpp index 8ef0198676..daa829f5a8 100644 --- a/libraries/gl/src/gl/GLShaders.cpp +++ b/libraries/gl/src/gl/GLShaders.cpp @@ -2,9 +2,12 @@ #include "GLLogging.h" +#include +#include + namespace gl { - +#pragma optimize("", off) #ifdef SEPARATE_PROGRAM bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const std::string& defines, GLuint &shaderObject, GLuint &programObject, std::string& error) { #else @@ -38,15 +41,15 @@ namespace gl { if (!compiled) { // save the source code to a temp file so we can debug easily - /* + std::ofstream filestream; - filestream.open("debugshader.glsl"); + filestream.open("D:\\debugshader.glsl"); if (filestream.is_open()) { - filestream << srcstr[0]; - filestream << srcstr[1]; - filestream.close(); + filestream << srcstr[0]; + filestream << srcstr[1]; + filestream.close(); } - */ + GLint infoLength = 0; glGetShaderiv(glshader, GL_INFO_LOG_LENGTH, &infoLength); @@ -55,13 +58,13 @@ namespace gl { glGetShaderInfoLog(glshader, infoLength, NULL, temp); - /* - filestream.open("debugshader.glsl.info.txt"); + + filestream.open("D:\\debugshader.glsl.info.txt"); if (filestream.is_open()) { - filestream << std::string(temp); - filestream.close(); + filestream << std::string(temp); + filestream.close(); } - */ + qCWarning(glLogging) << "GLShader::compileShader - failed to compile the gl shader object:"; for (auto s : srcstr) { @@ -74,7 +77,7 @@ namespace gl { delete[] temp; glDeleteShader(glshader); - return false; + exit(-1);// return false; } #ifdef SEPARATE_PROGRAM diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 25477b3b91..e2669ef4f6 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -21,7 +21,7 @@ Test::Test() { mismatchWindow.setModal(true); } -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory) { +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode) { // If a previous test results folder is found then wait for the user to delete it, or cancel // (e.g. the user may want to move the folder elsewhere) QString testResultsFolderPath { testDirectory + "/" + testResultsFolder }; @@ -70,9 +70,13 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage mismatchWindow.setTestFailure(testFailure); - mismatchWindow.exec(); + if (!interactiveMode) { + appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); + success = false; + } else { + mismatchWindow.exec(); - switch (mismatchWindow.getUserResponse()) { + switch (mismatchWindow.getUserResponse()) { case USER_RESPONSE_PASS: break; case USE_RESPONSE_FAIL: @@ -86,6 +90,7 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage default: assert(false); break; + } } } } @@ -144,7 +149,7 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg"); } -void Test::evaluateTests() { +void Test::evaluateTests(bool interactiveMode) { // Get list of JPEG images in folder, sorted by name QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); if (pathToImageDirectory == "") { @@ -177,7 +182,7 @@ void Test::evaluateTests() { exit(-1); } - bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory); + bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode); if (success) { messageBox.information(0, "Success", "All images are as expected"); @@ -189,7 +194,7 @@ void Test::evaluateTests() { // Two criteria are used to decide if a folder contains valid test results. // 1) a 'test'js' file exists in the folder // 2) the folder has the same number of actual and expected images -void Test::evaluateTestsRecursively() { +void Test::evaluateTestsRecursively(bool interactiveMode) { // Select folder to start recursing from QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); if (topLevelDirectory == "") { @@ -234,7 +239,7 @@ void Test::evaluateTestsRecursively() { } // Set success to false if any test has failed - success &= compareImageLists(expectedImages, resultImages, directory); + success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode); } if (success) { diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 73814c2311..422ac3a9c0 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -23,8 +23,8 @@ class Test { public: Test(); - void evaluateTests(); - void evaluateTestsRecursively(); + void evaluateTests(bool interactiveMode); + void evaluateTestsRecursively(bool interactiveMode); void createRecursiveScript(); void createTest(); @@ -53,7 +53,7 @@ private: ImageComparer imageComparer; - bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory); + bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode); }; #endif // hifi_test_h diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 105baddb92..efc2490828 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -12,14 +12,16 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); + + ui.checkBoxInteractiveMode->setChecked(true); } void AutoTester::on_evaluateTestsButton_clicked() { - test.evaluateTests(); + test.evaluateTests(ui.checkBoxInteractiveMode->isChecked()); } void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { - test.evaluateTestsRecursively(); + test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked()); } void AutoTester::on_createRecursiveScriptButton_clicked() { diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index acfea32ba1..99af639582 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -22,9 +22,9 @@ public: AutoTester(QWidget *parent = Q_NULLPTR); private slots: -void on_evaluateTestsButton_clicked(); -void on_evaluateTestsRecursivelyButton_clicked(); -void on_createRecursiveScriptButton_clicked(); + void on_evaluateTestsButton_clicked(); + void on_evaluateTestsRecursivelyButton_clicked(); + void on_createRecursiveScriptButton_clicked(); void on_createTestButton_clicked(); void on_closeButton_clicked(); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 7032ef9710..ceb8eaffeb 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 286 - 470 + 607 + 333 @@ -17,9 +17,9 @@ - 60 - 360 - 160 + 190 + 210 + 220 40 @@ -30,9 +30,9 @@ - 60 - 270 - 160 + 360 + 130 + 220 40 @@ -43,9 +43,9 @@ - 60 - 20 - 160 + 20 + 75 + 220 40 @@ -56,9 +56,9 @@ - 60 - 210 - 160 + 360 + 75 + 220 40 @@ -69,9 +69,9 @@ - 60 - 75 - 160 + 20 + 130 + 220 40 @@ -79,13 +79,29 @@ Evaluate Tests Recursively + + + + 23 + 40 + 131 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Interactive Mode + + 0 0 - 286 + 607 21 From 320045cfbdb479d75b5253095e032f28eb650ad4 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 15 Dec 2017 12:53:27 -0800 Subject: [PATCH 11/90] Modified by mistake. --- libraries/gl/src/gl/GLShaders.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/libraries/gl/src/gl/GLShaders.cpp b/libraries/gl/src/gl/GLShaders.cpp index daa829f5a8..8ef0198676 100644 --- a/libraries/gl/src/gl/GLShaders.cpp +++ b/libraries/gl/src/gl/GLShaders.cpp @@ -2,12 +2,9 @@ #include "GLLogging.h" -#include -#include - namespace gl { -#pragma optimize("", off) + #ifdef SEPARATE_PROGRAM bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const std::string& defines, GLuint &shaderObject, GLuint &programObject, std::string& error) { #else @@ -41,15 +38,15 @@ namespace gl { if (!compiled) { // save the source code to a temp file so we can debug easily - + /* std::ofstream filestream; - filestream.open("D:\\debugshader.glsl"); + filestream.open("debugshader.glsl"); if (filestream.is_open()) { - filestream << srcstr[0]; - filestream << srcstr[1]; - filestream.close(); + filestream << srcstr[0]; + filestream << srcstr[1]; + filestream.close(); } - + */ GLint infoLength = 0; glGetShaderiv(glshader, GL_INFO_LOG_LENGTH, &infoLength); @@ -58,13 +55,13 @@ namespace gl { glGetShaderInfoLog(glshader, infoLength, NULL, temp); - - filestream.open("D:\\debugshader.glsl.info.txt"); + /* + filestream.open("debugshader.glsl.info.txt"); if (filestream.is_open()) { - filestream << std::string(temp); - filestream.close(); + filestream << std::string(temp); + filestream.close(); } - + */ qCWarning(glLogging) << "GLShader::compileShader - failed to compile the gl shader object:"; for (auto s : srcstr) { @@ -77,7 +74,7 @@ namespace gl { delete[] temp; glDeleteShader(glshader); - exit(-1);// return false; + return false; } #ifdef SEPARATE_PROGRAM From bd5e2c9ab6cd30419d53076b9fddc3fdc303483e Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 15 Dec 2017 13:14:27 -0800 Subject: [PATCH 12/90] Cleanup per coding standard. --- tools/auto-tester/src/Test.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index e2669ef4f6..7e7baac6e8 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -77,19 +77,19 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage mismatchWindow.exec(); switch (mismatchWindow.getUserResponse()) { - case USER_RESPONSE_PASS: - break; - case USE_RESPONSE_FAIL: - appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); - success = false; - break; - case USER_RESPONSE_ABORT: - keepOn = false; - success = false; - break; - default: - assert(false); - break; + case USER_RESPONSE_PASS: + break; + case USE_RESPONSE_FAIL: + appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); + success = false; + break; + case USER_RESPONSE_ABORT: + keepOn = false; + success = false; + break; + default: + assert(false); + break; } } } From 0e7bc29649cc023e860f6d940a02276d2b3e11c0 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 15 Dec 2017 14:10:58 -0800 Subject: [PATCH 13/90] Added include for MacOS --- tools/auto-tester/src/ui/MismatchWindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index 711ead4fbe..fe22412522 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -11,6 +11,8 @@ #include +#include + MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { setupUi(this); @@ -35,7 +37,7 @@ QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultIma // The intensity value is modified to increase the brightness of the displayed image double absoluteDifference = fabs(p - q) / 255.0; - double modifiedDifference = pow(absoluteDifference, 0.5); + double modifiedDifference = sqrt(absoluteDifference); int difference = (int)(modifiedDifference * 255.0); From 8038f2bd9f60603c60f51b1db9c683e612a4bbef Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 15 Dec 2017 16:14:01 -0800 Subject: [PATCH 14/90] Added required parameter to test call. --- tools/auto-tester/src/Test.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 7e7baac6e8..2e938c7bc4 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -266,7 +266,8 @@ void Test::createRecursiveScript() { if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { messageBox.critical(0, "Internal Error", - "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\""); + "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\"" + ); exit(-1); } @@ -339,7 +340,7 @@ void Test::createRecursiveScript() { // The script produced will look as follows: // if (test1HasNotStarted) { // test1HasNotStarted = false; - // test1.test(); + // test1.test("auto"); // print("******started test 1******"); // } // | @@ -362,7 +363,7 @@ void Test::createRecursiveScript() { textStream << tab << tab << "if (test" << i - 1 << ".complete && test" << i << "HasNotStarted) {" << endl; } textStream << tab << tab << tab << "test" << i << "HasNotStarted = false;" << endl; - textStream << tab << tab << tab << "test" << i << "." << testFunction << "();" << endl; + textStream << tab << tab << tab << "test" << i << "." << testFunction << "(\"auto\");" << endl; textStream << tab << tab << tab << "print(\"******started test " << i << "******\");" << endl; textStream << tab << tab << "}" << endl << endl; From 116756c9762621c99e5206c3511d37ee26937e12 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 18 Dec 2017 11:22:43 +0100 Subject: [PATCH 15/90] 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 16/90] 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 074784d4b852315bfee09cb42efa14df7c4350f2 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 19 Dec 2017 14:14:53 -0800 Subject: [PATCH 17/90] Completed zipping of results. --- tools/auto-tester/CMakeLists.txt | 43 +++++++------ tools/auto-tester/src/Test.cpp | 75 ++++++++++++++++------ tools/auto-tester/src/Test.h | 12 +++- tools/auto-tester/src/ui/MismatchWindow.ui | 4 +- 4 files changed, 89 insertions(+), 45 deletions(-) diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index e5f2c1fb97..fe91c89352 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -1,24 +1,24 @@ -set(TARGET_NAME auto-tester) +set (TARGET_NAME auto-tester) project(${TARGET_NAME}) # Automatically run UIC and MOC. This replaces the older WRAP macros -SET(CMAKE_AUTOUIC ON) -SET(CMAKE_AUTOMOC ON) +SET (CMAKE_AUTOUIC ON) +SET (CMAKE_AUTOMOC ON) -setup_hifi_project(Core Widgets) -link_hifi_libraries() +setup_hifi_project (Core Widgets) +link_hifi_libraries () # FIX: Qt was built with -reduce-relocations if (Qt5_POSITION_INDEPENDENT_CODE) - SET(CMAKE_POSITION_INDEPENDENT_CODE ON) + SET (CMAKE_POSITION_INDEPENDENT_CODE ON) endif() # Qt includes -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${Qt5Core_INCLUDE_DIRS}) -include_directories(${Qt5Widgets_INCLUDE_DIRS}) +include_directories (${CMAKE_CURRENT_SOURCE_DIR}) +include_directories (${Qt5Core_INCLUDE_DIRS}) +include_directories (${Qt5Widgets_INCLUDE_DIRS}) -set(QT_LIBRARIES Qt5::Core Qt5::Widgets) +set (QT_LIBRARIES Qt5::Core Qt5::Widgets) # Find all sources files file (GLOB_RECURSE SOURCES src/*.cpp) @@ -27,21 +27,24 @@ file (GLOB_RECURSE UIS src/ui/*.ui) if (WIN32) # Do not show Console - set_property(TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) + set_property (TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) endif() -add_executable(PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS}) - -target_link_libraries(PROJECT_NAME ${QT_LIBRARIES}) +add_executable (PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS}) -# Copy required dll's. -add_custom_command(TARGET auto-tester POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ -) +target_zlib() +add_dependency_external_projects (quazip) +find_package (QuaZip REQUIRED) +target_include_directories( ${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) + +target_link_libraries (PROJECT_NAME ${QT_LIBRARIES}) + +package_libraries_for_deployment() if (WIN32) + add_paths_to_fixup_libs (${QUAZIP_DLL_PATH}) + find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) if (NOT WINDEPLOYQT_COMMAND) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 2e938c7bc4..fdce9b45fc 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -13,6 +13,9 @@ #include #include +#include +#include + Test::Test() { snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.+-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); @@ -21,23 +24,43 @@ Test::Test() { mismatchWindow.setModal(true); } -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode) { - // If a previous test results folder is found then wait for the user to delete it, or cancel - // (e.g. the user may want to move the folder elsewhere) - QString testResultsFolderPath { testDirectory + "/" + testResultsFolder }; - while (QDir().exists(testResultsFolderPath)) { - messageBox.setText("Previous test results have been found"); - messageBox.setInformativeText("Delete " + testResultsFolder + " before continuing"); - messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - int reply = messageBox.exec(); - if (reply == QMessageBox::Cancel) { - return false; +bool Test::createTestResultsFolderPathIfNeeded(QString directory) { + // The test results folder is located in the root of the tests (i.e. for recursive test evaluation) + if (testResultsFolderPath == "") { + testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER; + QDir testResultsFolder(testResultsFolderPath); + + if (testResultsFolder.exists()) { + testResultsFolder.removeRecursively(); } + + // Create a new test results folder + return QDir().mkdir(testResultsFolderPath); + } else { + return true; + } +} + +void Test::zipAndDeleteTestResultsFolder() { + QString zippedResultsFileName { testResultsFolderPath + ".zip" }; + QFileInfo fileInfo(zippedResultsFileName); + if (!fileInfo.exists()) { + QFile::remove(zippedResultsFileName); } - // Create a new test results folder - QDir().mkdir(testResultsFolderPath); + QDir testResultsFolder(testResultsFolderPath); + if (!testResultsFolder.isEmpty()) { + JlCompress::compressDir(testResultsFolderPath + ".zip", testResultsFolderPath); + } + testResultsFolder.removeRecursively(); + + //In all cases, for the next evaluation + testResultsFolderPath = ""; + index = 1; +} + +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode) { // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. const double THRESHOLD { 0.999 }; @@ -104,7 +127,6 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te exit(-1); } - static int index = 1; QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; if (!QDir().mkdir(failureFolderPath)) { messageBox.critical(0, "Internal error", "Failed to create folder " + failureFolderPath); @@ -112,10 +134,9 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te } ++index; - QString descriptionFileName { "ReadMe.txt" }; - QFile descriptionFile(failureFolderPath + "/" +descriptionFileName); + QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); if (!descriptionFile.open(QIODevice::ReadWrite)) { - messageBox.critical(0, "Internal error", "Failed to create file " + descriptionFileName); + messageBox.critical(0, "Internal error", "Failed to create file " + TEST_RESULTS_FILENAME); exit(-1); } @@ -156,6 +177,11 @@ void Test::evaluateTests(bool interactiveMode) { return; } + // Leave if test results folder could not be created + if (!createTestResultsFolderPathIfNeeded(pathToImageDirectory)) { + return; + } + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); // Separate images into two lists. The first is the expected images, the second is the test results @@ -189,6 +215,8 @@ void Test::evaluateTests(bool interactiveMode) { } else { messageBox.information(0, "Failure", "One or more images are not as expected"); } + + zipAndDeleteTestResultsFolder(); } // Two criteria are used to decide if a folder contains valid test results. @@ -201,6 +229,11 @@ void Test::evaluateTestsRecursively(bool interactiveMode) { return; } + // Leave if test results folder could not be created + if (!createTestResultsFolderPathIfNeeded(topLevelDirectory)) { + return; + } + bool success{ true }; QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { @@ -211,7 +244,7 @@ void Test::evaluateTestsRecursively(bool interactiveMode) { } // - const QString testPathname{ directory + "/" + testFilename }; + const QString testPathname{ directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (!fileInfo.exists()) { // Folder does not contain 'test.js' @@ -247,6 +280,8 @@ void Test::evaluateTestsRecursively(bool interactiveMode) { } else { messageBox.information(0, "Failure", "One or more images are not as expected"); } + + zipAndDeleteTestResultsFolder(); } void Test::importTest(QTextStream& textStream, const QString& testPathname, int testNumber) { @@ -282,7 +317,7 @@ void Test::createRecursiveScript() { QVector testPathnames; // First test if top-level folder has a test.js file - const QString testPathname{ topLevelDirectory + "/" + testFilename }; + const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -300,7 +335,7 @@ void Test::createRecursiveScript() { continue; } - const QString testPathname{ directory + "/" + testFilename }; + const QString testPathname{ directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 422ac3a9c0..dc65e3f665 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -37,10 +37,13 @@ public: void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); + bool createTestResultsFolderPathIfNeeded(QString directory); + void zipAndDeleteTestResultsFolder(); + private: - const QString testFilename{ "test.js" }; - const QString testResultsFolder{ "TestResults" }; - const QString testResultsFileName{ "TestResults.txt" }; + const QString TEST_FILENAME { "test.js" }; + const QString TEST_RESULTS_FOLDER { "TestResults" }; + const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; QMessageBox messageBox; @@ -54,6 +57,9 @@ private: ImageComparer imageComparer; bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode); + + QString testResultsFolderPath { "" }; + int index { 1 }; }; #endif // hifi_test_h diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui index 392bc1774b..5ecf966df5 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.ui +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -137,12 +137,12 @@ 210 790 - 75 + 121 23 - Abort Tests + Abort current test From 9835b0638ba29f2dcd32dd855f7722143be7046e Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 19 Dec 2017 14:40:23 -0800 Subject: [PATCH 18/90] Added progress bar. --- tools/auto-tester/src/Test.cpp | 19 +++++++++++++------ tools/auto-tester/src/Test.h | 8 +++++--- tools/auto-tester/src/ui/AutoTester.cpp | 6 ++++-- tools/auto-tester/src/ui/AutoTester.ui | 17 +++++++++++++++-- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index fdce9b45fc..8bad468afa 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -60,7 +60,12 @@ void Test::zipAndDeleteTestResultsFolder() { index = 1; } -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode) { +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar) { + progressBar->setMinimum(0); + progressBar->setMaximum(expectedImages.length() - 1); + progressBar->setValue(0); + progressBar->setVisible(true); + // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. const double THRESHOLD { 0.999 }; @@ -116,8 +121,11 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage } } } + + progressBar->setValue(i); } + progressBar->setVisible(false); return success; } @@ -170,7 +178,7 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg"); } -void Test::evaluateTests(bool interactiveMode) { +void Test::evaluateTests(bool interactiveMode, QProgressBar* progressBar) { // Get list of JPEG images in folder, sorted by name QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); if (pathToImageDirectory == "") { @@ -208,7 +216,7 @@ void Test::evaluateTests(bool interactiveMode) { exit(-1); } - bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode); + bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode, progressBar); if (success) { messageBox.information(0, "Success", "All images are as expected"); @@ -222,7 +230,7 @@ void Test::evaluateTests(bool interactiveMode) { // Two criteria are used to decide if a folder contains valid test results. // 1) a 'test'js' file exists in the folder // 2) the folder has the same number of actual and expected images -void Test::evaluateTestsRecursively(bool interactiveMode) { +void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar) { // Select folder to start recursing from QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); if (topLevelDirectory == "") { @@ -243,7 +251,6 @@ void Test::evaluateTestsRecursively(bool interactiveMode) { continue; } - // const QString testPathname{ directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (!fileInfo.exists()) { @@ -272,7 +279,7 @@ void Test::evaluateTestsRecursively(bool interactiveMode) { } // Set success to false if any test has failed - success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode); + success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode, progressBar); } if (success) { diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index dc65e3f665..37827e9e0b 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "ImageComparer.h" #include "ui/MismatchWindow.h" @@ -23,11 +24,13 @@ class Test { public: Test(); - void evaluateTests(bool interactiveMode); - void evaluateTestsRecursively(bool interactiveMode); + void evaluateTests(bool interactiveMode, QProgressBar* progressBar); + void evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar); void createRecursiveScript(); void createTest(); + bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar); + QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory); bool isInSnapshotFilenameFormat(QString filename); @@ -56,7 +59,6 @@ private: ImageComparer imageComparer; - bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode); QString testResultsFolderPath { "" }; int index { 1 }; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index efc2490828..1f1283b98b 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -14,14 +14,16 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); ui.checkBoxInteractiveMode->setChecked(true); + + ui.progressBar->setVisible(false); } void AutoTester::on_evaluateTestsButton_clicked() { - test.evaluateTests(ui.checkBoxInteractiveMode->isChecked()); + test.evaluateTests(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); } void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { - test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked()); + test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); } void AutoTester::on_createRecursiveScriptButton_clicked() { diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index ceb8eaffeb..544141975f 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -7,7 +7,7 @@ 0 0 607 - 333 + 395 @@ -18,7 +18,7 @@ 190 - 210 + 300 220 40 @@ -95,6 +95,19 @@ Interactive Mode + + + + 20 + 190 + 255 + 23 + + + + 24 + + From 4e59fecae2b7345851deff66bd744865c29f1022 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 20 Dec 2017 10:24:51 -0800 Subject: [PATCH 19/90] Added keylight and ambient light mode radio-buttons to the html (the UI). --- scripts/system/html/entityProperties.html | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 2ccad2c169..d3a0666b01 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -511,6 +511,11 @@
+
+ Inherit + Off + On +
@@ -531,11 +536,19 @@
-
+
+ +
+
+ Inherit + Off + On +
+
-
+
From d4b34b71adc26a5cf806b0e7c2281f7edbf349ff Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 20 Dec 2017 13:12:07 -0800 Subject: [PATCH 20/90] First version with packet update. --- .../entities/src/EntityItemProperties.cpp | 58 +++++++++++++++++++ libraries/entities/src/EntityItemProperties.h | 7 ++- libraries/entities/src/EntityPropertyFlags.h | 3 + libraries/entities/src/ZoneEntityItem.cpp | 38 +++++++++++- libraries/entities/src/ZoneEntityItem.h | 12 +++- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- 7 files changed, 118 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 678ddfcea5..9ef0efca6a 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -242,6 +242,64 @@ void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { } } +QString EntityItemProperties::getKeyLightModeAsString() const { + // return "enabled" if _keyLightMode is not valid + if (_keyLightMode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[_keyLightMode].second; + } else { + return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; + } +} + +QString EntityItemProperties::getKeyLightModeString(uint32_t mode) { + // return "enabled" if mode is not valid + if (mode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[mode].second; + } else { + return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; + } +} + +void EntityItemProperties::setKeyLightModeFromString(const QString& keyLightMode) { + auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { + return (pair.second == keyLightMode); + }); + + if (result != COMPONENT_MODES.end()) { + _keyLightMode = result->first; + _keyLightModeChanged = true; + } +} + +QString EntityItemProperties::getAmbientLightModeAsString() const { + // return "enabled" if _ambientLightMode is not valid + if (_ambientLightMode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[_ambientLightMode].second; + } else { + return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; + } +} + +QString EntityItemProperties::getAmbientLightModeString(uint32_t mode) { + // return "enabled" if mode is not valid + if (mode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[mode].second; + } else { + return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; + } +} + +void EntityItemProperties::setAmbientLightModeFromString(const QString& ambientLightMode) { + auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { + return (pair.second == ambientLightMode); + }); + + if (result != COMPONENT_MODES.end()) { + _ambientLightMode = result->first; + _ambientLightModeChanged = true; + } +} + EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 4f7ba1317b..e924ab3f94 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -180,6 +180,8 @@ public: DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup); DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); + DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); @@ -248,7 +250,8 @@ public: static QString getBackgroundModeString(BackgroundMode mode); static QString getHazeModeString(uint32_t mode); - + static QString getKeyLightModeString(uint32_t mode); + static QString getAmbientLightModeString(uint32_t mode); public: float getMaxDimension() const { return glm::compMax(_dimensions); } @@ -478,6 +481,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, HazeMode, hazeMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, KeyLightMode, keyLightMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 41c1e77bb8..73f6ec55c5 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -220,6 +220,9 @@ enum EntityPropertyList { PROP_HAZE_KEYLIGHT_RANGE, PROP_HAZE_KEYLIGHT_ALTITUDE, + PROP_KEY_LIGHT_MODE, + PROP_AMBIENT_LIGHT_MODE, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 0ed523202b..0701591eca 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -72,6 +72,9 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode); _hazeProperties.getProperties(properties); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightMode, getKeyLightMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(ambientLightMode, getAmbientLightMode); + return properties; } @@ -115,10 +118,13 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); - + SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); _hazePropertiesChanged = _hazeProperties.setProperties(properties); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode); + somethingChanged = somethingChanged || _keyLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged; return somethingChanged; @@ -173,6 +179,9 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromHaze; dataAt += bytesFromHaze; + READ_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); + return bytesRead; } @@ -236,6 +245,9 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); _hazeProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + + APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)getKeyLightMode()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)getAmbientLightMode()); } void ZoneEntityItem::debugDump() const { @@ -246,6 +258,8 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << " _backgroundMode:" << EntityItemProperties::getBackgroundModeString(_backgroundMode); qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getHazeModeString(_hazeMode); + qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getKeyLightModeString(_keyLightMode); + qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getAmbientLightModeString(_ambientLightMode); _keyLightProperties.debugDump(); _skyboxProperties.debugDump(); @@ -330,3 +344,25 @@ void ZoneEntityItem::setHazeMode(const uint32_t value) { uint32_t ZoneEntityItem::getHazeMode() const { return _hazeMode; } + +void ZoneEntityItem::setKeyLightMode(const uint32_t value) { + if (value < COMPONENT_MODE_ITEM_COUNT) { + _keyLightMode = value; + _keyLightPropertiesChanged = true; + } +} + +uint32_t ZoneEntityItem::getKeyLightMode() const { + return _keyLightMode; +} + +void ZoneEntityItem::setAmbientLightMode(const uint32_t value) { + if (value < COMPONENT_MODE_ITEM_COUNT) { + _ambientLightMode = value; + _ambientLightPropertiesChanged = true; + } +} + +uint32_t ZoneEntityItem::getAmbientLightMode() const { + return _ambientLightMode; +} diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 46e8a00c24..6a94d5c63b 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -73,6 +73,12 @@ public: void setHazeMode(const uint32_t value); uint32_t getHazeMode() const; + void setKeyLightMode(uint32_t value); + uint32_t getKeyLightMode() const; + + void setAmbientLightMode(uint32_t value); + uint32_t getAmbientLightMode() const; + SkyboxPropertyGroup getSkyboxProperties() const { return resultWithReadLock([&] { return _skyboxProperties; }); } const HazePropertyGroup& getHazeProperties() const { return _hazeProperties; } @@ -113,6 +119,8 @@ public: static const QString DEFAULT_FILTER_URL; static const uint32_t DEFAULT_HAZE_MODE{ (uint32_t)COMPONENT_MODE_INHERIT }; + static const uint32_t DEFAULT_KEY_LIGHT_MODE{ (uint32_t)COMPONENT_MODE_ENABLED }; // so as not to change previous behaviour + static const uint32_t DEFAULT_AMBIENT_LIGHT_MODE{ (uint32_t)COMPONENT_MODE_ENABLED }; protected: KeyLightPropertyGroup _keyLightProperties; @@ -121,8 +129,9 @@ protected: QString _compoundShapeURL; BackgroundMode _backgroundMode = BACKGROUND_MODE_INHERIT; - uint32_t _hazeMode{ DEFAULT_HAZE_MODE }; + uint32_t _keyLightMode{ DEFAULT_KEY_LIGHT_MODE }; + uint32_t _ambientLightMode{ DEFAULT_AMBIENT_LIGHT_MODE }; SkyboxPropertyGroup _skyboxProperties; HazePropertyGroup _hazeProperties; @@ -138,6 +147,7 @@ protected: bool _skyboxPropertiesChanged { false }; bool _hazePropertiesChanged{ false }; bool _stagePropertiesChanged { false }; + bool _ambientLightPropertiesChanged { false }; static bool _drawZoneBoundaries; static bool _zonesArePickable; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 62354da11a..3aec01792e 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::StaticCertJsonVersionOne); + return static_cast(EntityVersion::ZoneLightInheritModes); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::ConnectionIdentifier); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 618ac2de0c..7af59284e3 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -200,7 +200,8 @@ enum class EntityVersion : PacketVersion { StrokeColorProperty = 77, HasDynamicOwnershipTests, HazeEffect, - StaticCertJsonVersionOne + StaticCertJsonVersionOne, + ZoneLightInheritModes }; enum class EntityScriptCallMethodVersion : PacketVersion { From 58c6f8e9c477dbad312230923acf77da7626c084 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 20 Dec 2017 19:17:18 -0800 Subject: [PATCH 21/90] Radio buttons now work correctly. --- .../src/RenderableZoneEntityItem.cpp | 23 ++++++++-- .../src/RenderableZoneEntityItem.h | 6 ++- .../entities/src/EntityItemProperties.cpp | 29 ++++++++++++- libraries/entities/src/ZoneEntityItem.cpp | 3 ++ scripts/system/html/entityProperties.html | 12 +++--- scripts/system/html/js/entityProperties.js | 42 +++++++++++++++++-- 6 files changed, 98 insertions(+), 17 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 04da70d733..900b0d7392 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -164,13 +164,20 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { } if (_visible) { - // FInally, push the light visible in the frame - // THe directional key light for sure - _stage->_currentFrame.pushSunLight(_sunIndex); + // Finally, push the light visible in the frame + if (_keyLightMode == COMPONENT_MODE_ENABLED) { + _stage->_currentFrame.pushSunLight(_sunIndex); + } else if (_keyLightMode == COMPONENT_MODE_DISABLED) { + // DEAL WITH OFF LIGHT + } // The ambient light only if it has a valid texture to render with if (_validAmbientTexture || _validSkyboxTexture) { - _stage->_currentFrame.pushAmbientLight(_ambientIndex); + if (_ambientLightMode == COMPONENT_MODE_ENABLED) { + _stage->_currentFrame.pushAmbientLight(_ambientIndex); + } else if (_ambientLightMode == COMPONENT_MODE_DISABLED) { + // DEAL WITH OFF LIGHT + } } // The background only if the mode is not inherit @@ -483,6 +490,14 @@ void ZoneEntityRenderer::setHazeMode(ComponentMode mode) { _hazeMode = mode; } +void ZoneEntityRenderer::setKeyLightMode(ComponentMode mode) { + _keyLightMode = mode; +} + +void ZoneEntityRenderer::setAmbientLightMode(ComponentMode mode) { + _ambientLightMode = mode; +} + void ZoneEntityRenderer::setSkyboxColor(const glm::vec3& color) { editSkybox()->setColor(color); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 050a8a4386..f7f8ecb6ee 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -56,6 +56,8 @@ private: void setSkyboxURL(const QString& skyboxUrl); void setBackgroundMode(BackgroundMode mode); void setHazeMode(ComponentMode mode); + void setKeyLightMode(ComponentMode mode); + void setAmbientLightMode(ComponentMode mode); void setSkyboxColor(const glm::vec3& color); void setProceduralUserData(const QString& userData); @@ -85,7 +87,9 @@ private: const model::HazePointer _haze{ std::make_shared() }; BackgroundMode _backgroundMode{ BACKGROUND_MODE_INHERIT }; - ComponentMode _hazeMode{ COMPONENT_MODE_INHERIT }; + ComponentMode _hazeMode { COMPONENT_MODE_INHERIT }; + ComponentMode _keyLightMode { COMPONENT_MODE_ENABLED }; + ComponentMode _ambientLightMode { COMPONENT_MODE_ENABLED }; indexed_container::Index _sunIndex{ LightStage::INVALID_INDEX }; indexed_container::Index _shadowIndex{ LightStage::INVALID_INDEX }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 9ef0efca6a..3984627499 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -388,6 +388,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode); CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); + CHECK_PROPERTY_CHANGE(PROP_KEY_LIGHT_MODE, keyLightMode); + CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -622,6 +624,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString()); _haze.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_KEY_LIGHT_MODE, keyLightMode, getKeyLightModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AMBIENT_LIGHT_MODE, ambientLightMode, getAmbientLightModeAsString()); } // Web only @@ -808,8 +813,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(backgroundMode, BackgroundMode); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(hazeMode, HazeMode); + COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(keyLightMode, KeyLightMode); + COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(ambientLightMode, AmbientLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); @@ -966,8 +972,9 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(collisionSoundURL); COPY_PROPERTY_IF_CHANGED(backgroundMode); - COPY_PROPERTY_IF_CHANGED(hazeMode); + COPY_PROPERTY_IF_CHANGED(keyLightMode); + COPY_PROPERTY_IF_CHANGED(ambientLightMode); COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); @@ -1241,6 +1248,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_RANGE, Haze, haze, HazeKeyLightRange, hazeKeyLightRange); ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_ALTITUDE, Haze, haze, HazeKeyLightAltitude, hazeKeyLightAltitude); + ADD_PROPERTY_TO_MAP(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t); // FIXME - these are not yet handled @@ -1485,6 +1495,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); _staticHaze.setProperties(properties); _staticHaze.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + + + APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1835,6 +1849,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode); properties.getHaze().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); } if (properties.getType() == EntityTypes::PolyVox) { @@ -2415,6 +2432,14 @@ QList EntityItemProperties::listChangedProperties() { out += "hazeMode"; } + if (keyLightModeChanged()) { + out += "keyLightMode"; + } + + if (ambientLightModeChanged()) { + out += "ambientLightMode"; + } + if (voxelVolumeSizeChanged()) { out += "voxelVolumeSize"; } diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 0701591eca..3b30774656 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -211,6 +211,9 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_HAZE_MODE; requestedProperties += _hazeProperties.getEntityProperties(params); + requestedProperties += PROP_KEY_LIGHT_MODE; + requestedProperties += PROP_AMBIENT_LIGHT_MODE; + return requestedProperties; } diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index d3a0666b01..bc98dcc25f 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -512,9 +512,9 @@
- Inherit - Off - On + Inherit + Off + On
@@ -540,9 +540,9 @@
- Inherit - Off - On + Inherit + Off + On
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 0ab9f7a9cb..0fd88d9f19 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -627,7 +627,6 @@ function loaded() { var elHyperlinkHref = document.getElementById("property-hyperlink-href"); - var elTextText = document.getElementById("property-text-text"); var elTextLineHeight = document.getElementById("property-text-line-height"); var elTextTextColor = document.getElementById("property-text-text-color"); @@ -641,6 +640,10 @@ function loaded() { var elZoneStageSunModelEnabled = document.getElementById("property-zone-stage-sun-model-enabled"); + var elZoneKeyLightModeInherit = document.getElementById("property-zone-key-light-mode-inherit"); + var elZoneKeyLightModeDisabled = document.getElementById("property-zone-key-light-mode-disabled"); + var elZoneKeyLightModeEnabled = document.getElementById("property-zone-key-light-mode-enabled"); + var elZoneKeyLightColor = document.getElementById("property-zone-key-light-color"); var elZoneKeyLightColorRed = document.getElementById("property-zone-key-light-color-red"); var elZoneKeyLightColorGreen = document.getElementById("property-zone-key-light-color-green"); @@ -649,6 +652,11 @@ function loaded() { var elZoneKeyLightAmbientIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); + + var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); + var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); + var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); + var elZoneKeyLightAmbientURL = document.getElementById("property-zone-key-ambient-url"); var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); @@ -1002,7 +1010,13 @@ function loaded() { elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); elLightExponent.value = properties.exponent.toFixed(2); elLightCutoff.value = properties.cutoff.toFixed(2); + } else if (properties.type === "Zone") { + + elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); + elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); + elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); + elZoneStageSunModelEnabled.checked = properties.stage.sunModelEnabled; elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; @@ -1013,6 +1027,11 @@ function loaded() { elZoneKeyLightAmbientIntensity.value = properties.keyLight.ambientIntensity.toFixed(2); elZoneKeyLightDirectionX.value = properties.keyLight.direction.x.toFixed(2); elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); + + elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); + elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); + elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); + elZoneKeyLightAmbientURL.value = properties.keyLight.ambientURL; elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); @@ -1400,6 +1419,13 @@ function loaded() { } })); + var keyLightModeChanged = createZoneComponentModeChangedFunction('keyLightMode', + elZoneKeyLightModeInherit, elZoneKeyLightModeDisabled, elZoneKeyLightModeEnabled); + + elZoneKeyLightModeInherit.addEventListener('change', keyLightModeChanged); + elZoneKeyLightModeDisabled.addEventListener('change', keyLightModeChanged); + elZoneKeyLightModeEnabled.addEventListener('change', keyLightModeChanged); + elZoneStageSunModelEnabled.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage', 'sunModelEnabled')); colorPickers.push($('#property-zone-key-light-color').colpick({ @@ -1425,6 +1451,14 @@ function loaded() { elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); + + var ambientLightModeChanged = createZoneComponentModeChangedFunction('ambientLightMode', + elZoneAmbientLightModeInherit, elZoneAmbientLightModeDisabled, elZoneAmbientLightModeEnabled); + + elZoneAmbientLightModeInherit.addEventListener('change', ambientLightModeChanged); + elZoneAmbientLightModeDisabled.addEventListener('change', ambientLightModeChanged); + elZoneAmbientLightModeEnabled.addEventListener('change', ambientLightModeChanged); + elZoneKeyLightAmbientIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity')); elZoneKeyLightAmbientURL.addEventListener('change', @@ -1435,9 +1469,9 @@ function loaded() { elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); - var hazeModeChanged = - createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit, - elZoneHazeModeDisabled, elZoneHazeModeEnabled); + var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', + elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled); + elZoneHazeModeInherit.addEventListener('change', hazeModeChanged); elZoneHazeModeDisabled.addEventListener('change', hazeModeChanged); elZoneHazeModeEnabled.addEventListener('change', hazeModeChanged); From 72ef716b13e8febbea1e1ddc4443684c86459558 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 21 Dec 2017 09:52:06 +0100 Subject: [PATCH 22/90] 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 e974cac177abf69ce2bdaa4da148e5722452c455 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 21 Dec 2017 19:19:55 -0800 Subject: [PATCH 23/90] Keylight inheritance mode works. --- .../src/RenderableZoneEntityItem.cpp | 21 +++++++------- .../src/RenderableZoneEntityItem.h | 4 +-- libraries/render-utils/src/LightStage.cpp | 28 +++++++++++++++++++ libraries/render-utils/src/LightStage.h | 17 +++++++++++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 900b0d7392..97d403e4d3 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -165,18 +165,14 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { if (_visible) { // Finally, push the light visible in the frame - if (_keyLightMode == COMPONENT_MODE_ENABLED) { + if (_keyLightMode != BACKGROUND_MODE_INHERIT) { _stage->_currentFrame.pushSunLight(_sunIndex); - } else if (_keyLightMode == COMPONENT_MODE_DISABLED) { - // DEAL WITH OFF LIGHT } // The ambient light only if it has a valid texture to render with if (_validAmbientTexture || _validSkyboxTexture) { - if (_ambientLightMode == COMPONENT_MODE_ENABLED) { + if (_ambientLightMode != BACKGROUND_MODE_INHERIT) { _stage->_currentFrame.pushAmbientLight(_ambientIndex); - } else if (_ambientLightMode == COMPONENT_MODE_DISABLED) { - // DEAL WITH OFF LIGHT } } @@ -201,7 +197,6 @@ void ZoneEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& Parent::removeFromScene(scene, transaction); } - void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { DependencyManager::get()->updateZone(entity->getID()); @@ -244,11 +239,11 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateKeyZoneItemFromEntity(); if (sunChanged) { - updateKeySunFromEntity(); + updateKeySunFromEntity(entity); } if (sunChanged || skyboxChanged) { - updateKeyAmbientFromEntity(); + updateKeyAmbientFromEntity(entity); } if (backgroundChanged || skyboxChanged) { @@ -317,7 +312,9 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return false; } -void ZoneEntityRenderer::updateKeySunFromEntity() { +void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity) { + setKeyLightMode((ComponentMode)entity->getKeyLightMode()); + const auto& sunLight = editSunLight(); sunLight->setType(model::Light::SUN); sunLight->setPosition(_lastPosition); @@ -329,7 +326,9 @@ void ZoneEntityRenderer::updateKeySunFromEntity() { sunLight->setDirection(_keyLightProperties.getDirection()); } -void ZoneEntityRenderer::updateKeyAmbientFromEntity() { +void ZoneEntityRenderer::updateKeyAmbientFromEntity(const TypedEntityPointer& entity) { + setAmbientLightMode((ComponentMode)entity->getAmbientLightMode()); + const auto& ambientLight = editAmbientLight(); ambientLight->setType(model::Light::AMBIENT); ambientLight->setPosition(_lastPosition); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index f7f8ecb6ee..bf6bda2af8 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -46,8 +46,8 @@ protected: private: void updateKeyZoneItemFromEntity(); - void updateKeySunFromEntity(); - void updateKeyAmbientFromEntity(); + void updateKeySunFromEntity(const TypedEntityPointer& entity); + void updateKeyAmbientFromEntity(const TypedEntityPointer& entity); void updateHazeFromEntity(const TypedEntityPointer& entity); void updateKeyBackgroundFromEntity(const TypedEntityPointer& entity); void updateAmbientMap(); diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index e568554452..0cc30495b8 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -28,6 +28,34 @@ static const auto MAX_BIAS = 0.006f; const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; LightStage::LightStage() { + // Add off lights + const LightPointer ambientOffLight { std::make_shared() }; + ambientOffLight->setAmbientIntensity(0.0f); + ambientOffLight->setColor(model::Vec3(0.0)); + ambientOffLight->setIntensity(0.0f); + ambientOffLight->setType(model::Light::Type::AMBIENT); + _ambientOffLight = 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); + + 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); + + 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); } LightStage::Shadow::Schema::Schema() { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 508e67ec17..5ba0b02131 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -185,6 +185,11 @@ public: Frame _currentFrame; + Index getAmbientOffLight() { return _ambientOffLight; } + Index getPointOffLight() { return _pointOffLight; } + Index getSpotOffLight() { return _spotOffLight; } + Index getSunOffLight() { return _sunOffLight; } + protected: struct Desc { @@ -199,6 +204,18 @@ protected: Descs _descs; LightMap _lightMap; + // define off lights + + 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; + }; using LightStagePointer = std::shared_ptr; From 752952507d9c03a13eb151e753471b72052bca00 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 22 Dec 2017 14:18:02 +0100 Subject: [PATCH 24/90] 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 25/90] 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 26/90] 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 502cac2ac71a9569aae3f6fdedb5b2ed96359908 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 22 Dec 2017 08:05:12 -0800 Subject: [PATCH 27/90] Keylight inherit/off/on working --- .../src/RenderableZoneEntityItem.cpp | 12 +++++++++++- .../entities-renderer/src/RenderableZoneEntityItem.h | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 97d403e4d3..41edb5b020 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -165,7 +165,17 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { if (_visible) { // Finally, push the light visible in the frame - if (_keyLightMode != BACKGROUND_MODE_INHERIT) { + if (_keyLightMode == COMPONENT_MODE_DISABLED && sunOnIndex == NO_STORED_VALUE) { + // Just turned off, store previous value before changing + sunOnIndex = _sunIndex; + _sunIndex = _stage->getSunOffLight(); + } else if (_keyLightMode == COMPONENT_MODE_ENABLED && sunOnIndex != NO_STORED_VALUE) { + // Just turned on, restore previous value before clearing stored value + _sunIndex = sunOnIndex; + sunOnIndex = NO_STORED_VALUE; + } + + if (_keyLightMode != COMPONENT_MODE_INHERIT) { _stage->_currentFrame.pushSunLight(_sunIndex); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index bf6bda2af8..fe3f1a2a14 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -95,6 +95,9 @@ private: indexed_container::Index _shadowIndex{ LightStage::INVALID_INDEX }; indexed_container::Index _ambientIndex{ LightStage::INVALID_INDEX }; + const int NO_STORED_VALUE { -1 }; + indexed_container::Index sunOnIndex { NO_STORED_VALUE }; + BackgroundStagePointer _backgroundStage; BackgroundStage::Index _backgroundIndex{ BackgroundStage::INVALID_INDEX }; From e102b8c08767b4f717d51d2eb62616bfd1250d27 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 22 Dec 2017 08:35:09 -0800 Subject: [PATCH 28/90] Fixed Ubuntu warning. --- libraries/entities-renderer/src/RenderableZoneEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 41edb5b020..80f9836c1c 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -181,7 +181,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { // The ambient light only if it has a valid texture to render with if (_validAmbientTexture || _validSkyboxTexture) { - if (_ambientLightMode != BACKGROUND_MODE_INHERIT) { + if (_ambientLightMode != COMPONENT_MODE_INHERIT) { _stage->_currentFrame.pushAmbientLight(_ambientIndex); } } From 5018edcfe00b701fdc5bcef243957a8fdd1150dd Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 22 Dec 2017 12:48:05 -0800 Subject: [PATCH 29/90] 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 3f82e9147b5ea2a6e7ea3d9519e27821a2fed6d0 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 22 Dec 2017 16:30:19 -0800 Subject: [PATCH 30/90] Change keylight/ambient mode default from "enabled" to "inherit" --- .../entities-renderer/src/RenderableZoneEntityItem.h | 4 ++-- libraries/entities/src/EntityItemProperties.h | 4 ++-- libraries/entities/src/ZoneEntityItem.h | 12 ++++-------- scripts/system/html/entityProperties.html | 8 ++++---- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index fe3f1a2a14..437aea8d0f 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -88,8 +88,8 @@ private: BackgroundMode _backgroundMode{ BACKGROUND_MODE_INHERIT }; ComponentMode _hazeMode { COMPONENT_MODE_INHERIT }; - ComponentMode _keyLightMode { COMPONENT_MODE_ENABLED }; - ComponentMode _ambientLightMode { COMPONENT_MODE_ENABLED }; + ComponentMode _keyLightMode { COMPONENT_MODE_INHERIT }; + ComponentMode _ambientLightMode { COMPONENT_MODE_INHERIT }; indexed_container::Index _sunIndex{ LightStage::INVALID_INDEX }; indexed_container::Index _shadowIndex{ LightStage::INVALID_INDEX }; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index e924ab3f94..c5dbb99d69 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -180,8 +180,8 @@ public: DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup); DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); - DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); + DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 6a94d5c63b..a020fa90ba 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -118,20 +118,16 @@ public: static const bool DEFAULT_GHOSTING_ALLOWED; static const QString DEFAULT_FILTER_URL; - static const uint32_t DEFAULT_HAZE_MODE{ (uint32_t)COMPONENT_MODE_INHERIT }; - static const uint32_t DEFAULT_KEY_LIGHT_MODE{ (uint32_t)COMPONENT_MODE_ENABLED }; // so as not to change previous behaviour - static const uint32_t DEFAULT_AMBIENT_LIGHT_MODE{ (uint32_t)COMPONENT_MODE_ENABLED }; - protected: KeyLightPropertyGroup _keyLightProperties; ShapeType _shapeType = DEFAULT_SHAPE_TYPE; QString _compoundShapeURL; - BackgroundMode _backgroundMode = BACKGROUND_MODE_INHERIT; - uint32_t _hazeMode{ DEFAULT_HAZE_MODE }; - uint32_t _keyLightMode{ DEFAULT_KEY_LIGHT_MODE }; - uint32_t _ambientLightMode{ DEFAULT_AMBIENT_LIGHT_MODE }; + BackgroundMode _backgroundMode { BACKGROUND_MODE_INHERIT }; + uint32_t _hazeMode { COMPONENT_MODE_INHERIT }; + uint32_t _keyLightMode { COMPONENT_MODE_INHERIT }; + uint32_t _ambientLightMode { COMPONENT_MODE_INHERIT }; SkyboxPropertyGroup _skyboxProperties; HazePropertyGroup _hazeProperties; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index bc98dcc25f..c914ec34d4 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -512,9 +512,9 @@
- Inherit + Inherit Off - On + On
@@ -540,9 +540,9 @@
- Inherit + Inherit Off - On + On
From 0b73e7db3f663942455807c6502d0bec385102e9 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 26 Dec 2017 09:15:13 -0800 Subject: [PATCH 31/90] WIP - adding AmbientLightPropertyGroup --- .../src/RenderableZoneEntityItem.cpp | 6 +- .../src/RenderableZoneEntityItem.h | 1 + .../src/AmbientLightPropertyGroup.cpp | 155 ++++++++++++++++++ .../entities/src/AmbientLightPropertyGroup.h | 83 ++++++++++ .../entities/src/EntityItemProperties.cpp | 1 - libraries/entities/src/EntityItemProperties.h | 1 + libraries/entities/src/EntityPropertyFlags.h | 7 +- .../entities/src/KeyLightPropertyGroup.cpp | 65 ++------ .../entities/src/KeyLightPropertyGroup.h | 2 - libraries/entities/src/ZoneEntityItem.h | 1 + 10 files changed, 260 insertions(+), 62 deletions(-) create mode 100644 libraries/entities/src/AmbientLightPropertyGroup.cpp create mode 100644 libraries/entities/src/AmbientLightPropertyGroup.h diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 80f9836c1c..85c5baff97 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -346,12 +346,12 @@ void ZoneEntityRenderer::updateKeyAmbientFromEntity(const TypedEntityPointer& en // Set the keylight - ambientLight->setAmbientIntensity(_keyLightProperties.getAmbientIntensity()); + ambientLight->setAmbientIntensity(_ambientLightProperties.getAmbientIntensity()); - if (_keyLightProperties.getAmbientURL().isEmpty()) { + if (_ambientLightProperties.getAmbientURL().isEmpty()) { setAmbientURL(_skyboxProperties.getURL()); } else { - setAmbientURL(_keyLightProperties.getAmbientURL()); + setAmbientURL(_ambientLightProperties.getAmbientURL()); } } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 437aea8d0f..0e3bed941f 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -111,6 +111,7 @@ private: bool _needHazeUpdate{ true }; KeyLightPropertyGroup _keyLightProperties; + AmbientLightPropertyGroup _ambientLightProperties; HazePropertyGroup _hazeProperties; StagePropertyGroup _stageProperties; SkyboxPropertyGroup _skyboxProperties; diff --git a/libraries/entities/src/AmbientLightPropertyGroup.cpp b/libraries/entities/src/AmbientLightPropertyGroup.cpp new file mode 100644 index 0000000000..dbcb0eef75 --- /dev/null +++ b/libraries/entities/src/AmbientLightPropertyGroup.cpp @@ -0,0 +1,155 @@ +// +// AmbientLightPropertyGroup.cpp +// libraries/entities/src +// +// Created by Nissim Hadar on 2017/12/24. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AmbientLightPropertyGroup.h" + +#include +#include + +#include "EntityItemProperties.h" +#include "EntityItemPropertiesMacros.h" + +const float AmbientLightPropertyGroup::DEFAULT_AMBIENT_LIGHT_INTENSITY = 0.5f; + +void AmbientLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, + QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { + + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_AMBIENT_LIGHT_INTENSITY, AmbientLight, ambientLight, AmbientIntensity, ambientIntensity); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_AMBIENT_LIGHT_URL, AmbientLight, ambientLight, AmbientURL, ambientURL); +} + +void AmbientLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ambientLight, ambientIntensity, float, setAmbientIntensity); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ambientLight, ambientURL, QString, setAmbientURL); + + // legacy property support + COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(ambientLightAmbientIntensity, float, setAmbientIntensity, getAmbientIntensity); +} + +void AmbientLightPropertyGroup::merge(const AmbientLightPropertyGroup& other) { + COPY_PROPERTY_IF_CHANGED(ambientIntensity); + COPY_PROPERTY_IF_CHANGED(ambientURL); +} + +void AmbientLightPropertyGroup::debugDump() const { + qCDebug(entities) << " AmbientLightPropertyGroup: ---------------------------------------------"; + qCDebug(entities) << " ambientIntensity:" << getAmbientIntensity(); + qCDebug(entities) << " ambientURL:" << getAmbientURL(); +} + +void AmbientLightPropertyGroup::listChangedProperties(QList& out) { + if (ambientIntensityChanged()) { + out << "ambientLight-ambientIntensity"; + } + if (ambientURLChanged()) { + out << "ambientLight-ambientURL"; + } +} + +bool AmbientLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, getAmbientIntensity()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_URL, getAmbientURL()); + + return true; +} + +bool AmbientLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, + int& processedBytes) { + + int bytesRead = 0; + bool overwriteLocalData = true; + bool somethingChanged = false; + + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, float, setAmbientIntensity); + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_URL, QString, setAmbientURL); + + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_AMBIENT_LIGHT_INTENSITY, AmbientIntensity); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_AMBIENT_LIGHT_URL, AmbientURL); + + processedBytes += bytesRead; + + Q_UNUSED(somethingChanged); + + return true; +} + +void AmbientLightPropertyGroup::markAllChanged() { + _ambientIntensityChanged = true; + _ambientURLChanged = true; +} + +EntityPropertyFlags AmbientLightPropertyGroup::getChangedProperties() const { + EntityPropertyFlags changedProperties; + + CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_INTENSITY, ambientIntensity); + CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_URL, ambientURL); + + return changedProperties; +} + +void AmbientLightPropertyGroup::getProperties(EntityItemProperties& properties) const { + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(AmbientLight, AmbientIntensity, getAmbientIntensity); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(AmbientLight, AmbientURL, getAmbientURL); +} + +bool AmbientLightPropertyGroup::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(AmbientLight, AmbientIntensity, ambientIntensity, setAmbientIntensity); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(AmbientLight, AmbientURL, ambientURL, setAmbientURL); + + return somethingChanged; +} + +EntityPropertyFlags AmbientLightPropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties; + + requestedProperties += PROP_AMBIENT_LIGHT_INTENSITY; + requestedProperties += PROP_AMBIENT_LIGHT_URL; + + return requestedProperties; +} + +void AmbientLightPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, getAmbientIntensity()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_URL, getAmbientURL()); +} + +int AmbientLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, float, setAmbientIntensity); + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_URL, QString, setAmbientURL); + + return bytesRead; +} diff --git a/libraries/entities/src/AmbientLightPropertyGroup.h b/libraries/entities/src/AmbientLightPropertyGroup.h new file mode 100644 index 0000000000..fbbc7c9900 --- /dev/null +++ b/libraries/entities/src/AmbientLightPropertyGroup.h @@ -0,0 +1,83 @@ +// +// AmbientLightPropertyGroup.h +// libraries/entities/src +// +// Created by Nissim Hadar on 2017/12/24. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#ifndef hifi_AmbientLightPropertyGroup_h +#define hifi_AmbientLightPropertyGroup_h + +#include + +#include + +#include +#include "EntityItemPropertiesMacros.h" +#include "PropertyGroup.h" + +class EntityItemProperties; +class EncodeBitstreamParams; +class OctreePacketData; +class EntityTreeElementExtraEncodeData; +class ReadBitstreamToTreeParams; + +class AmbientLightPropertyGroup : public PropertyGroup { +public: + // EntityItemProperty related helpers + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, + QScriptEngine* engine, bool skipDefaults, + EntityItemProperties& defaultEntityProperties) const override; + virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + + void merge(const AmbientLightPropertyGroup& other); + + virtual void debugDump() const override; + virtual void listChangedProperties(QList& out) override; + + virtual bool appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags, + const unsigned char*& dataAt, int& processedBytes) override; + virtual void markAllChanged() override; + virtual EntityPropertyFlags getChangedProperties() const override; + + // EntityItem related helpers + // methods for getting/setting all properties of an entity + virtual void getProperties(EntityItemProperties& propertiesOut) const override; + + /// returns true if something changed + virtual bool setProperties(const EntityItemProperties& properties) override; + + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + static const float DEFAULT_AMBIENT_LIGHT_INTENSITY; + + DEFINE_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, AmbientIntensity, ambientIntensity, float, DEFAULT_AMBIENT_LIGHT_INTENSITY); + DEFINE_PROPERTY_REF(PROP_AMBIENT_LIGHT_URL, AmbientURL, ambientURL, QString, ""); +}; + +#endif // hifi_AmbientLightPropertyGroup_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 570f12fc9b..01ae7d36f9 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1162,7 +1162,6 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); - ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLightAmbientIntensity, keyLightAmbientIntensity, float); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index c5dbb99d69..3de76e3777 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -172,6 +172,7 @@ public: DEFINE_PROPERTY(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float, particle::DEFAULT_RADIUS_FINISH); DEFINE_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, EmitterShouldTrail, emitterShouldTrail, bool, particle::DEFAULT_EMITTER_SHOULD_TRAIL); DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup); + DEFINE_PROPERTY_GROUP(AmbientLight, ambientLight, AmbientLightPropertyGroup); DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE); DEFINE_PROPERTY_REF(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray, PolyVoxEntityItem::DEFAULT_VOXEL_DATA); DEFINE_PROPERTY_REF(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t, PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 73f6ec55c5..8317bad05f 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -244,7 +244,6 @@ enum EntityPropertyList { // the size of the properties bitflags mask PROP_KEYLIGHT_COLOR = PROP_COLOR, PROP_KEYLIGHT_INTENSITY = PROP_INTENSITY, - PROP_KEYLIGHT_AMBIENT_INTENSITY = PROP_CUTOFF, PROP_KEYLIGHT_DIRECTION = PROP_EXPONENT, PROP_STAGE_SUN_MODEL_ENABLED = PROP_IS_SPOTLIGHT, PROP_STAGE_LATITUDE = PROP_DIFFUSE_COLOR, @@ -257,8 +256,10 @@ enum EntityPropertyList { PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, PROP_SKYBOX_URL = PROP_ANIMATION_FPS, - PROP_KEYLIGHT_AMBIENT_URL = PROP_ANIMATION_PLAYING, - + + PROP_AMBIENT_LIGHT_INTENSITY = PROP_CUTOFF, + PROP_AMBIENT_LIGHT_URL = PROP_ANIMATION_PLAYING, + // Aliases/Piggyback properties for Web. These properties intentionally reuse the enum values for // other properties which will never overlap with each other. PROP_SOURCE_URL = PROP_MODEL_URL, diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index 4246da309b..4bf8818c99 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -17,54 +17,41 @@ #include "EntityItemProperties.h" #include "EntityItemPropertiesMacros.h" - const xColor KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR = { 255, 255, 255 }; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY = 1.0f; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY = 0.5f; const glm::vec3 KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION = { 0.0f, -1.0f, 0.0f }; -void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { - +void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, + QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_COLOR, KeyLight, keyLight, Color, color); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_INTENSITY, KeyLight, keyLight, Intensity, intensity); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLight, keyLight, AmbientIntensity, ambientIntensity); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_DIRECTION, KeyLight, keyLight, Direction, direction); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_AMBIENT_URL, KeyLight, keyLight, AmbientURL, ambientURL); - } void KeyLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, color, xColor, setColor); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, intensity, float, setIntensity); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, ambientIntensity, float, setAmbientIntensity); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, direction, glmVec3, setDirection); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, ambientURL, QString, setAmbientURL); // legacy property support COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightColor, xColor, setColor, getColor); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightIntensity, float, setIntensity, getIntensity); - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightAmbientIntensity, float, setAmbientIntensity, getAmbientIntensity); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightDirection, glmVec3, setDirection, getDirection); } void KeyLightPropertyGroup::merge(const KeyLightPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(color); COPY_PROPERTY_IF_CHANGED(intensity); - COPY_PROPERTY_IF_CHANGED(ambientIntensity); COPY_PROPERTY_IF_CHANGED(direction); - COPY_PROPERTY_IF_CHANGED(ambientURL); } - - void KeyLightPropertyGroup::debugDump() const { qCDebug(entities) << " KeyLightPropertyGroup: ---------------------------------------------"; qCDebug(entities) << " color:" << getColor(); // << "," << getColor()[1] << "," << getColor()[2]; qCDebug(entities) << " intensity:" << getIntensity(); qCDebug(entities) << " direction:" << getDirection(); - qCDebug(entities) << " ambientIntensity:" << getAmbientIntensity(); - qCDebug(entities) << " ambientURL:" << getAmbientURL(); } void KeyLightPropertyGroup::listChangedProperties(QList& out) { @@ -77,52 +64,39 @@ void KeyLightPropertyGroup::listChangedProperties(QList& out) { if (directionChanged()) { out << "keyLight-direction"; } - if (ambientIntensityChanged()) { - out << "keyLight-ambientIntensity"; - } - if (ambientURLChanged()) { - out << "keyLight-ambientURL"; - } } - bool KeyLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, getAmbientIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_URL, getAmbientURL()); return true; } - -bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes) { - +bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, + int& processedBytes) { + int bytesRead = 0; bool overwriteLocalData = true; bool somethingChanged = false; READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, float, setAmbientIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_URL, QString, setAmbientURL); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_COLOR, Color); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_INTENSITY, Intensity); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_AMBIENT_INTENSITY, AmbientIntensity); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_DIRECTION, Direction); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_AMBIENT_URL, AmbientURL); processedBytes += bytesRead; @@ -134,9 +108,7 @@ bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFl void KeyLightPropertyGroup::markAllChanged() { _colorChanged = true; _intensityChanged = true; - _ambientIntensityChanged = true; _directionChanged = true; - _ambientURLChanged = true; } EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { @@ -144,20 +116,15 @@ EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_COLOR, color); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_INTENSITY, intensity); - CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_AMBIENT_INTENSITY, ambientIntensity); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, direction); - CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_AMBIENT_URL, ambientURL); return changedProperties; } -void KeyLightPropertyGroup::getProperties(EntityItemProperties& properties) const { - +void KeyLightPropertyGroup::getProperties(EntityItemProperties& properties) const { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Color, getColor); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Intensity, getIntensity); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, AmbientIntensity, getAmbientIntensity); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Direction, getDirection); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, AmbientURL, getAmbientURL); } bool KeyLightPropertyGroup::setProperties(const EntityItemProperties& properties) { @@ -165,9 +132,7 @@ bool KeyLightPropertyGroup::setProperties(const EntityItemProperties& properties SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Color, color, setColor); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Intensity, intensity, setIntensity); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, AmbientIntensity, ambientIntensity, setAmbientIntensity); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Direction, direction, setDirection); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, AmbientURL, ambientURL, setAmbientURL); return somethingChanged; } @@ -177,9 +142,7 @@ EntityPropertyFlags KeyLightPropertyGroup::getEntityProperties(EncodeBitstreamPa requestedProperties += PROP_KEYLIGHT_COLOR; requestedProperties += PROP_KEYLIGHT_INTENSITY; - requestedProperties += PROP_KEYLIGHT_AMBIENT_INTENSITY; requestedProperties += PROP_KEYLIGHT_DIRECTION; - requestedProperties += PROP_KEYLIGHT_AMBIENT_URL; return requestedProperties; } @@ -196,9 +159,7 @@ void KeyLightPropertyGroup::appendSubclassData(OctreePacketData* packetData, Enc APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, getAmbientIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_URL, getAmbientURL()); } int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, @@ -211,9 +172,7 @@ int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, float, setAmbientIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_URL, QString, setAmbientURL); return bytesRead; } diff --git a/libraries/entities/src/KeyLightPropertyGroup.h b/libraries/entities/src/KeyLightPropertyGroup.h index 210d410bd9..f33ebb282d 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -81,9 +81,7 @@ public: DEFINE_PROPERTY_REF(PROP_KEYLIGHT_COLOR, Color, color, xColor, DEFAULT_KEYLIGHT_COLOR); DEFINE_PROPERTY(PROP_KEYLIGHT_INTENSITY, Intensity, intensity, float, DEFAULT_KEYLIGHT_INTENSITY); - DEFINE_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, AmbientIntensity, ambientIntensity, float, DEFAULT_KEYLIGHT_AMBIENT_INTENSITY); DEFINE_PROPERTY_REF(PROP_KEYLIGHT_DIRECTION, Direction, direction, glm::vec3, DEFAULT_KEYLIGHT_DIRECTION); - DEFINE_PROPERTY_REF(PROP_KEYLIGHT_AMBIENT_URL, AmbientURL, ambientURL, QString, ""); }; #endif // hifi_KeyLightPropertyGroup_h diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index a020fa90ba..6f60ffaca9 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -13,6 +13,7 @@ #define hifi_ZoneEntityItem_h #include "KeyLightPropertyGroup.h" +#include "AmbientLightPropertyGroup.h" #include "EntityItem.h" #include "EntityTree.h" #include "SkyboxPropertyGroup.h" From 1fa8f7c55bd19b383aa9ba16214bbb31be4f6e8f Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 26 Dec 2017 09:15:45 -0800 Subject: [PATCH 32/90] WIP - adding AmbientLightPropertyGroup --- libraries/entities/src/KeyLightPropertyGroup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index 4bf8818c99..61d48f7cb1 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -93,7 +93,7 @@ bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFl 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); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_DIRECTION, Direction); From 4ffd896cedbe7d611d83d26201cc57e00b6b7175 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 26 Dec 2017 11:15:33 -0800 Subject: [PATCH 33/90] WIP - adding AmbientLightPropertyGroup --- .../src/RenderableZoneEntityItem.cpp | 16 +++++--- .../src/RenderableZoneEntityItem.h | 2 +- .../entities/src/EntityItemProperties.cpp | 15 ++++++- libraries/entities/src/ZoneEntityItem.cpp | 35 ++++++++++++++-- libraries/entities/src/ZoneEntityItem.h | 5 ++- scripts/system/html/js/entityProperties.js | 40 ++++++++++++------- 6 files changed, 85 insertions(+), 28 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 85c5baff97..e6af7bcdd0 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -212,7 +212,8 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen // FIXME one of the bools here could become true between being fetched and being reset, // resulting in a lost update - bool sunChanged = entity->keyLightPropertiesChanged(); + bool keyLightChanged = entity->keyLightPropertiesChanged(); + bool ambientLightChanged = entity->ambientLightPropertiesChanged(); bool backgroundChanged = entity->backgroundPropertiesChanged(); bool skyboxChanged = entity->skyboxPropertiesChanged(); bool hazeChanged = entity->hazePropertiesChanged(); @@ -223,6 +224,7 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen _lastDimensions = entity->getDimensions(); _keyLightProperties = entity->getKeyLightProperties(); + _ambientLightProperties = entity->getAmbientLightProperties(); _skyboxProperties = entity->getSkyboxProperties(); _hazeProperties = entity->getHazeProperties(); _stageProperties = entity->getStageProperties(); @@ -248,12 +250,12 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateKeyZoneItemFromEntity(); - if (sunChanged) { + if (keyLightChanged) { updateKeySunFromEntity(entity); } - if (sunChanged || skyboxChanged) { - updateKeyAmbientFromEntity(entity); + if (ambientLightChanged || skyboxChanged) { + updateAmbientLightFromEntity(entity); } if (backgroundChanged || skyboxChanged) { @@ -279,9 +281,11 @@ ItemKey ZoneEntityRenderer::getKey() { bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (entity->keyLightPropertiesChanged() || + entity->ambientLightPropertiesChanged() || entity->backgroundPropertiesChanged() || entity->hazePropertiesChanged() || entity->skyboxPropertiesChanged()) { + return true; } @@ -336,7 +340,7 @@ void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity sunLight->setDirection(_keyLightProperties.getDirection()); } -void ZoneEntityRenderer::updateKeyAmbientFromEntity(const TypedEntityPointer& entity) { +void ZoneEntityRenderer::updateAmbientLightFromEntity(const TypedEntityPointer& entity) { setAmbientLightMode((ComponentMode)entity->getAmbientLightMode()); const auto& ambientLight = editAmbientLight(); @@ -345,7 +349,7 @@ void ZoneEntityRenderer::updateKeyAmbientFromEntity(const TypedEntityPointer& en ambientLight->setOrientation(_lastRotation); - // Set the keylight + // Set the ambient light ambientLight->setAmbientIntensity(_ambientLightProperties.getAmbientIntensity()); if (_ambientLightProperties.getAmbientURL().isEmpty()) { diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 0e3bed941f..f7473f1898 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -47,7 +47,7 @@ protected: private: void updateKeyZoneItemFromEntity(); void updateKeySunFromEntity(const TypedEntityPointer& entity); - void updateKeyAmbientFromEntity(const TypedEntityPointer& entity); + void updateAmbientLightFromEntity(const TypedEntityPointer& entity); void updateHazeFromEntity(const TypedEntityPointer& entity); void updateKeyBackgroundFromEntity(const TypedEntityPointer& entity); void updateAmbientMap(); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 01ae7d36f9..d13569e260 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -35,6 +35,7 @@ SkyboxPropertyGroup EntityItemProperties::_staticSkybox; HazePropertyGroup EntityItemProperties::_staticHaze; StagePropertyGroup EntityItemProperties::_staticStage; KeyLightPropertyGroup EntityItemProperties::_staticKeyLight; +AmbientLightPropertyGroup EntityItemProperties::_staticAmbientLight; EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1); @@ -79,6 +80,7 @@ void EntityItemProperties::debugDump() const { getSkybox().debugDump(); getHaze().debugDump(); getKeyLight().debugDump(); + getAmbientLight().debugDump(); qCDebug(entities) << " changed properties..."; EntityPropertyFlags props = getChangedProperties(); @@ -438,6 +440,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); + changedProperties += _ambientLight.getChangedProperties(); changedProperties += _skybox.getChangedProperties(); changedProperties += _stage.getChangedProperties(); changedProperties += _haze.getChangedProperties(); @@ -612,6 +615,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Zones only if (_type == EntityTypes::Zone) { _keyLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + _ambientLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BACKGROUND_MODE, backgroundMode, getBackgroundModeAsString()); @@ -845,6 +849,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool _animation.copyFromScriptValue(object, _defaultSettings); _keyLight.copyFromScriptValue(object, _defaultSettings); + _ambientLight.copyFromScriptValue(object, _defaultSettings); _skybox.copyFromScriptValue(object, _defaultSettings); _stage.copyFromScriptValue(object, _defaultSettings); _haze.copyFromScriptValue(object, _defaultSettings); @@ -994,6 +999,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { _animation.merge(other._animation); _keyLight.merge(other._keyLight); + _ambientLight.merge(other._ambientLight); _skybox.merge(other._skybox); _stage.merge(other._stage); _haze.merge(other._haze); @@ -1476,6 +1482,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticKeyLight.setProperties(properties); _staticKeyLight.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + _staticAmbientLight.setProperties(properties); + _staticAmbientLight.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + _staticStage.setProperties(properties); _staticStage.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -1495,7 +1504,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticHaze.setProperties(properties); _staticHaze.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode()); } @@ -1834,7 +1842,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int } if (properties.getType() == EntityTypes::Zone) { - properties.getKeyLight().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); + properties.getKeyLight().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + properties.getAmbientLight().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); properties.getStage().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); @@ -2079,6 +2088,7 @@ void EntityItemProperties::markAllChanged() { _staticCertificateVersionChanged = true; _keyLight.markAllChanged(); + _ambientLight.markAllChanged(); _backgroundModeChanged = true; _hazeModeChanged = true; @@ -2540,6 +2550,7 @@ QList EntityItemProperties::listChangedProperties() { getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); + getAmbientLight().listChangedProperties(out); getSkybox().listChangedProperties(out); getStage().listChangedProperties(out); getHaze().listChangedProperties(out); diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 3b30774656..bab7d8c7b7 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -53,7 +53,11 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr withReadLock([&] { _keyLightProperties.getProperties(properties); }); - + + withReadLock([&] { + _ambientLightProperties.getProperties(properties); + }); + _stageProperties.getProperties(properties); COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); @@ -103,6 +107,9 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie withWriteLock([&] { _keyLightPropertiesChanged = _keyLightProperties.setProperties(properties); }); + withWriteLock([&] { + _ambientLightPropertiesChanged = _ambientLightProperties.setProperties(properties); + }); _stagePropertiesChanged = _stageProperties.setProperties(properties); @@ -125,7 +132,8 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode); - somethingChanged = somethingChanged || _keyLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged; + somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || + _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged; return somethingChanged; } @@ -147,6 +155,16 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromKeylight; dataAt += bytesFromKeylight; + int bytesFromAmbientlight; + withWriteLock([&] { + bytesFromAmbientlight = _ambientLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _ambientLightPropertiesChanged); + }); + + somethingChanged = somethingChanged || _ambientLightPropertiesChanged; + bytesRead += bytesFromAmbientlight; + dataAt += bytesFromAmbientlight; + int bytesFromStage = _stageProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, _stagePropertiesChanged); somethingChanged = somethingChanged || _stagePropertiesChanged; @@ -194,6 +212,10 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += _keyLightProperties.getEntityProperties(params); }); + withReadLock([&] { + requestedProperties += _ambientLightProperties.getEntityProperties(params); + }); + requestedProperties += _stageProperties.getEntityProperties(params); requestedProperties += PROP_SHAPE_TYPE; @@ -228,10 +250,13 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits bool successPropertyFits = true; _keyLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); + propertyFlags, propertiesDidntFit, propertyCount, appendState); + + _ambientLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); _stageProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); + propertyFlags, propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); @@ -265,6 +290,7 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getAmbientLightModeString(_ambientLightMode); _keyLightProperties.debugDump(); + _ambientLightProperties.debugDump(); _skyboxProperties.debugDump(); _hazeProperties.debugDump(); _stageProperties.debugDump(); @@ -330,6 +356,7 @@ QString ZoneEntityItem::getCompoundShapeURL() const { void ZoneEntityItem::resetRenderingPropertiesChanged() { withWriteLock([&] { _keyLightPropertiesChanged = false; + _ambientLightPropertiesChanged = false; _backgroundPropertiesChanged = false; _skyboxPropertiesChanged = false; _hazePropertiesChanged = false; diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 6f60ffaca9..53c011b5ad 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -67,6 +67,7 @@ public: virtual void setCompoundShapeURL(const QString& url); KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock([&] { return _keyLightProperties; }); } + AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock([&] { return _ambientLightProperties; }); } void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; _backgroundPropertiesChanged = true; } BackgroundMode getBackgroundMode() const { return _backgroundMode; } @@ -94,6 +95,7 @@ public: void setFilterURL(const QString url); bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; } + bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; } bool backgroundPropertiesChanged() const { return _backgroundPropertiesChanged; } bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; } @@ -121,6 +123,7 @@ public: protected: KeyLightPropertyGroup _keyLightProperties; + AmbientLightPropertyGroup _ambientLightProperties; ShapeType _shapeType = DEFAULT_SHAPE_TYPE; QString _compoundShapeURL; @@ -140,11 +143,11 @@ protected: // Dirty flags turn true when either keylight properties is changing values. bool _keyLightPropertiesChanged { false }; + bool _ambientLightPropertiesChanged { false }; bool _backgroundPropertiesChanged{ false }; bool _skyboxPropertiesChanged { false }; bool _hazePropertiesChanged{ false }; bool _stagePropertiesChanged { false }; - bool _ambientLightPropertiesChanged { false }; static bool _drawZoneBoundaries; static bool _zonesArePickable; diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 0fd88d9f19..5d788726f6 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -640,6 +640,7 @@ function loaded() { var elZoneStageSunModelEnabled = document.getElementById("property-zone-stage-sun-model-enabled"); + // Key light var elZoneKeyLightModeInherit = document.getElementById("property-zone-key-light-mode-inherit"); var elZoneKeyLightModeDisabled = document.getElementById("property-zone-key-light-mode-disabled"); var elZoneKeyLightModeEnabled = document.getElementById("property-zone-key-light-mode-enabled"); @@ -649,16 +650,18 @@ function loaded() { var elZoneKeyLightColorGreen = document.getElementById("property-zone-key-light-color-green"); var elZoneKeyLightColorBlue = document.getElementById("property-zone-key-light-color-blue"); var elZoneKeyLightIntensity = document.getElementById("property-zone-key-intensity"); - var elZoneKeyLightAmbientIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); + // Ambient light var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); - var elZoneKeyLightAmbientURL = document.getElementById("property-zone-key-ambient-url"); + var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); + var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); + // Haze var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); var elZoneHazeModeDisabled = document.getElementById("property-zone-haze-mode-disabled"); var elZoneHazeModeEnabled = document.getElementById("property-zone-haze-mode-enabled"); @@ -1012,7 +1015,7 @@ function loaded() { elLightCutoff.value = properties.cutoff.toFixed(2); } else if (properties.type === "Zone") { - + // Key light elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); @@ -1024,16 +1027,18 @@ function loaded() { elZoneKeyLightColorGreen.value = properties.keyLight.color.green; elZoneKeyLightColorBlue.value = properties.keyLight.color.blue; elZoneKeyLightIntensity.value = properties.keyLight.intensity.toFixed(2); - elZoneKeyLightAmbientIntensity.value = properties.keyLight.ambientIntensity.toFixed(2); elZoneKeyLightDirectionX.value = properties.keyLight.direction.x.toFixed(2); elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); + // Ambient light elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); - elZoneKeyLightAmbientURL.value = properties.keyLight.ambientURL; + elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); + elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; + // Haze elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); @@ -1399,6 +1404,7 @@ function loaded() { var textBackgroundColorChangeFunction = createEmitColorPropertyUpdateFunction( 'backgroundColor', elTextBackgroundColorRed, elTextBackgroundColorGreen, elTextBackgroundColorBlue); + elTextBackgroundColorRed.addEventListener('change', textBackgroundColorChangeFunction); elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction); elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction); @@ -1419,6 +1425,7 @@ function loaded() { } })); + // Key light var keyLightModeChanged = createZoneComponentModeChangedFunction('keyLightMode', elZoneKeyLightModeInherit, elZoneKeyLightModeDisabled, elZoneKeyLightModeEnabled); @@ -1446,12 +1453,20 @@ function loaded() { })); var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); + elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); + var zoneKeyLightDirectionChangeFunction = createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', + elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); + + elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); + elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); + + // Ambient light var ambientLightModeChanged = createZoneComponentModeChangedFunction('ambientLightMode', elZoneAmbientLightModeInherit, elZoneAmbientLightModeDisabled, elZoneAmbientLightModeEnabled); @@ -1459,16 +1474,13 @@ function loaded() { elZoneAmbientLightModeDisabled.addEventListener('change', ambientLightModeChanged); elZoneAmbientLightModeEnabled.addEventListener('change', ambientLightModeChanged); - elZoneKeyLightAmbientIntensity.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity')); - elZoneKeyLightAmbientURL.addEventListener('change', - createEmitGroupTextPropertyUpdateFunction('keyLight', 'ambientURL')); - var zoneKeyLightDirectionChangeFunction = - createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', - elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); - elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); - elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); + elZoneAmbientLightIntensity.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('ambientLight', 'ambientIntensity')); + elZoneAmbientLightURL.addEventListener('change', + createEmitGroupTextPropertyUpdateFunction('ambientLight', 'ambientURL')); + + // Haze var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled); From 9b9295aaecdd4d1352463429d521ecc4dd641568 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 27 Dec 2017 14:17:05 -0800 Subject: [PATCH 34/90] Script that uses date, time and coordinates to compute sun position - this is then used to position the keylight. --- scripts/developer/sunModel.js | 277 ++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 scripts/developer/sunModel.js diff --git a/scripts/developer/sunModel.js b/scripts/developer/sunModel.js new file mode 100644 index 0000000000..dc0753cd73 --- /dev/null +++ b/scripts/developer/sunModel.js @@ -0,0 +1,277 @@ +// +// sunModel.js +// scripts/developer +// +// Created by Nissim Hadar on 2017/12/27. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Code is based on the NOAA model - see https://www.esrl.noaa.gov/gmd/grad/solcalc/ +// +(function() { + // Utility functions for trig. calculations + function toRadians(angle_degs) { + return angle_degs * (Math.PI / 180); + } + function toDegrees(angle_rads) { + return angle_rads * (180.0 / Math.PI); + } + + // Parameters + var latitude_degs = 47.751033; + var longitude_degs = -122.228176; + + // These are used a lot + var latitude = toRadians(latitude_degs); + var longitude = toRadians(longitude_degs); + + // Code to check if Daylight Savings is active + Date.prototype.stdTimezoneOffset = function() { + var fy = this.getFullYear(); + if (!Date.prototype.stdTimezoneOffset.cache.hasOwnProperty(fy)) { + var maxOffset = new Date(fy, 0, 1).getTimezoneOffset(); + var monthsTestOrder = [6, 7, 5, 8, 4, 9, 3, 10, 2, 11, 1]; + + for(var mi = 0;mi < 12; ++mi) { + var offset = new Date(fy, monthsTestOrder[mi], 1).getTimezoneOffset(); + if (offset != maxOffset) { + maxOffset = Math.max(maxOffset, offset); + break; + } + } + Date.prototype.stdTimezoneOffset.cache[fy] = maxOffset; + } + return Date.prototype.stdTimezoneOffset.cache[fy]; + }; + + // Cache the result for per year stdTimezoneOffset so that you don't need to recalculate it when testing multiple dates in + // the same year. + Date.prototype.stdTimezoneOffset.cache = {}; + + Date.prototype.isDST = function() { + return this.getTimezoneOffset() < this.stdTimezoneOffset(); + }; + + // The Julian Date is the number of days (fractional) that have elapsed since Jan 1st, 4713 BC + function getJulianDate(dateTime) { + var month = dateTime.getMonth() + 1; + var day = dateTime.getDate() + 1; + var year = dateTime.getFullYear(); + + if (month <= 2) { + year -= 1; + month += 12; + } + + var A = Math.floor(year / 100); + var B = 2 - A + Math.floor(A / 4); + return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5; + } + + function getMinutes(dateTime) { + var hours = dateTime.getHours(); + var minutes = dateTime.getMinutes(); + var seconds = dateTime.getSeconds(); + + if (Date.prototype.isDST()) { + hour -= 1; + } + + return hours * 60 + minutes + seconds / 60.0; + } + + function calcGeomMeanAnomalySun(t) { + var M = 357.52911 + t * (35999.05029 - 0.0001537 * t); + return M; // in degrees + } + + function calcSunEqOfCenter(t) { + var m = calcGeomMeanAnomalySun(t); + var mrad = toRadians(m); + var sinm = Math.sin(mrad); + var sin2m = Math.sin(mrad + mrad); + var sin3m = Math.sin(mrad + mrad + mrad); + var C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289; + return C; // in degrees + } + + function calcGeomMeanLongSun(t) { + var L0 = 280.46646 + t * (36000.76983 + t*(0.0003032)) + while(L0 > 360.0) { + L0 -= 360.0 + } + while(L0 < 0.0) { + L0 += 360.0 + } + return L0 // in degrees + } + + function calcSunTrueLong(t) { + var l0 = calcGeomMeanLongSun(t); + var c = calcSunEqOfCenter(t); + var O = l0 + c; + return O; // in degrees + } + + function calcSunApparentLong(t) { + var o = calcSunTrueLong(t); + var omega = 125.04 - 1934.136 * t; + var lambda = o - 0.00569 - 0.00478 * Math.sin(toRadians(omega)); + return lambda; // in degrees + } + + function calcMeanObliquityOfEcliptic(t) { + var seconds = 21.448 - t * (46.8150 + t * (0.00059 - t * (0.001813))); + var e0 = 23.0 + (26.0 + (seconds / 60.0)) / 60.0; + return e0; // in degrees + } + + function calcObliquityCorrection(t) { + var e0 = calcMeanObliquityOfEcliptic(t); + var omega = 125.04 - 1934.136 * t; + var e = e0 + 0.00256 * Math.cos(toRadians(omega)); + return e; // in degrees + } + + function calcSunDeclination(t) { + var e = calcObliquityCorrection(t); + var lambda = calcSunApparentLong(t); + + var sint = Math.sin(toRadians(e)) * Math.sin(toRadians(lambda)); + var theta = toDegrees(Math.asin(sint)); + return theta; // in degrees + } + + function calcEccentricityEarthOrbit(t) { + var e = 0.016708634 - t * (0.000042037 + 0.0000001267 * t); + return e; // unitless + } + + function calcEquationOfTime(t) { + var epsilon = calcObliquityCorrection(t); + var l0 = calcGeomMeanLongSun(t); + var e = calcEccentricityEarthOrbit(t); + var m = calcGeomMeanAnomalySun(t); + + var y = Math.tan(toRadians(epsilon) / 2.0); + y *= y; + + var sin2l0 = Math.sin(2.0 * toRadians(l0)); + var sinm = Math.sin(toRadians(m)); + var cos2l0 = Math.cos(2.0 * toRadians(l0)); + var sin4l0 = Math.sin(4.0 * toRadians(l0)); + var sin2m = Math.sin(2.0 * toRadians(m)); + + var Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m; + return toDegrees(Etime) * 4.0; // in minutes of time + } + + function calcSunTrueAnomaly(t) { + var m = calcGeomMeanAnomalySun(t); + var c = calcSunEqOfCenter(t); + var v = m + c; + return v; // in degrees + } + + function calcSunRadVector(t) { + var v = calcSunTrueAnomaly(t); + var e = calcEccentricityEarthOrbit(t); + var R = (1.000001018 * (1 - e * e)) / (1 + e * Math.cos(toRadians(v))); + return R; // in AUs + } + + var COMPUTATION_CYCLE = 5000; // Run every 5 seconds + this.preload = function(entityID) { // You don't have the entityID before the preload + Script.setInterval( + function() { + var dateTime = new Date(); + + var julianDay = getJulianDate(dateTime); + var localTimeMinutes = getMinutes(dateTime); + var timeZone = -dateTime.getTimezoneOffset() / 60; + var totalTime = julianDay + localTimeMinutes/1440.0 - timeZone / 24.0; + var julianCentralTime = (julianDay - 2451545.0)/36525.0; + var eqTime = calcEquationOfTime(julianCentralTime) + var theta_rads = toRadians(calcSunDeclination(julianCentralTime)); + var solarTimeFix = eqTime + 4.0 * longitude_degs - 60.0 * timeZone; + var earthRadVec = calcSunRadVector(julianCentralTime); + + var trueSolarTime = localTimeMinutes + solarTimeFix; + while (trueSolarTime > 1440) { + trueSolarTime -= 1440; + } + + var hourAngle = trueSolarTime / 4.0 - 180.0; + if (hourAngle < -180.0) { + hourAngle += 360.0; + } + var hourAngleRadians = toRadians(hourAngle); + + var csz = Math.sin(latitude) * Math.sin(theta_rads) + + Math.cos(latitude) * Math.cos(theta_rads) * Math.cos(hourAngleRadians); + csz = Math.min(1.0, Math.max(-1.0, csz)); + + var zenith = toDegrees(Math.acos(csz)); + var azDenom = ( Math.cos(latitude) * Math.sin(toRadians(zenith))); + if (Math.abs(azDenom) > 0.001) { + azRad = (( Math.sin(latitude) * Math.cos(toRadians(zenith)) ) - Math.sin(theta_rads)) / azDenom; + if (Math.abs(azRad) > 1.0) { + if (azRad < 0.0) { + azRad = -1.0; + } else { + azRad = 1.0; + } + } + var solarAzimuth_degs = 180.0 - toDegrees(Math.acos(azRad)) + if (hourAngle > 0.0) { + solarAzimuth_degs = -solarAzimuth_degs; + } + } else { + if (latitude_degs > 0.0) { + solarAzimuth_degs = 180.0; + } else { + solarAzimuth_degs = 0.0; + } + } + if (solarAzimuth_degs < 0.0) { + solarAzimuth_degs += 360.0; + } + + // Atmospheric Refraction correction + var exoatmElevation = 90.0 - zenith; + if (exoatmElevation > 85.0) { + var refractionCorrection = 0.0; + } else { + var te = Math.tan(toRadians(exoatmElevation)); + if (exoatmElevation > 5.0) { + var refractionCorrection = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te); + } else if (exoatmElevation > -0.575) { + var refractionCorrection = + 1735.0 + exoatmElevation * + (-518.2 + exoatmElevation * (103.4 + exoatmElevation * (-12.79 + exoatmElevation * 0.711))); + } else { + var refractionCorrection = -20.774 / te; + } + refractionCorrection = refractionCorrection / 3600.0; + } + + var solarZenith = zenith - refractionCorrection; + var solarAltitude_degs = 90.0 - solarZenith; // aka solar elevation + + // Convert to XYZ + var solarAltitude = toRadians(solarAltitude_degs); + var solarAzimuth = toRadians(solarAzimuth_degs); + + var xPos = Math.cos(solarAltitude) * Math.sin(solarAzimuth); + var zPos = Math.cos(solarAltitude) * Math.cos(solarAzimuth); + var yPos = -Math.sin(solarAltitude); + + Entities.editEntity(entityID, { keyLight : {direction: { x: xPos, y: yPos, z: zPos }}}); + }, + + COMPUTATION_CYCLE + ); + }; +}); \ No newline at end of file From 03cf7710d0fbf011dba7cefbd0ee1e98a0deb3d2 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 28 Dec 2017 09:34:55 -0800 Subject: [PATCH 35/90] Ambient light inheritance --- .../src/RenderableZoneEntityItem.cpp | 27 ++++++++++++------- .../src/RenderableZoneEntityItem.h | 3 ++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index e6af7bcdd0..6e6b87a6e5 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -165,14 +165,14 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { if (_visible) { // Finally, push the light visible in the frame - if (_keyLightMode == COMPONENT_MODE_DISABLED && sunOnIndex == NO_STORED_VALUE) { + if (_keyLightMode == COMPONENT_MODE_DISABLED && _sunOnIndex == NO_STORED_VALUE) { // Just turned off, store previous value before changing - sunOnIndex = _sunIndex; + _sunOnIndex = _sunIndex; _sunIndex = _stage->getSunOffLight(); - } else if (_keyLightMode == COMPONENT_MODE_ENABLED && sunOnIndex != NO_STORED_VALUE) { + } else if (_keyLightMode == COMPONENT_MODE_ENABLED && _sunOnIndex != NO_STORED_VALUE) { // Just turned on, restore previous value before clearing stored value - _sunIndex = sunOnIndex; - sunOnIndex = NO_STORED_VALUE; + _sunIndex = _sunOnIndex; + _sunOnIndex = NO_STORED_VALUE; } if (_keyLightMode != COMPONENT_MODE_INHERIT) { @@ -180,10 +180,19 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { } // The ambient light only if it has a valid texture to render with - if (_validAmbientTexture || _validSkyboxTexture) { - if (_ambientLightMode != COMPONENT_MODE_INHERIT) { - _stage->_currentFrame.pushAmbientLight(_ambientIndex); - } + if (_ambientLightMode == COMPONENT_MODE_DISABLED && _ambientOnIndex == NO_STORED_VALUE) { + // Just turned off, store previous value before changing + _ambientOnIndex = _ambientIndex; + _ambientIndex = _stage->getAmbientOffLight(); + } + else if (_ambientLightMode == COMPONENT_MODE_ENABLED && _ambientOnIndex != NO_STORED_VALUE) { + // Just turned on, restore previous value before clearing stored value + _ambientIndex = _ambientOnIndex; + _ambientOnIndex = NO_STORED_VALUE; + } + + if (_ambientLightMode != COMPONENT_MODE_INHERIT && (_validAmbientTexture || _validSkyboxTexture)) { + _stage->_currentFrame.pushAmbientLight(_ambientIndex); } // The background only if the mode is not inherit diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index f7473f1898..f753d39b97 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -96,7 +96,8 @@ private: indexed_container::Index _ambientIndex{ LightStage::INVALID_INDEX }; const int NO_STORED_VALUE { -1 }; - indexed_container::Index sunOnIndex { NO_STORED_VALUE }; + indexed_container::Index _sunOnIndex { NO_STORED_VALUE }; + indexed_container::Index _ambientOnIndex { NO_STORED_VALUE }; BackgroundStagePointer _backgroundStage; BackgroundStage::Index _backgroundIndex{ BackgroundStage::INVALID_INDEX }; From 8bb4d1431cd8d32cd7604fd8e07228b62b8f6360 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 28 Dec 2017 09:35:40 -0800 Subject: [PATCH 36/90] WIP - copy skylight URL to ambient URL. --- scripts/system/edit.js | 6 +- scripts/system/html/entityProperties.html | 72 ++++++++++++---------- scripts/system/html/js/entityProperties.js | 9 +++ 3 files changed, 53 insertions(+), 34 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index e28f877d85..ae3f88bd4e 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2044,9 +2044,9 @@ var PropertiesTool = function (opts) { // If any of the natural dimensions are not 0, resize if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && - naturalDimensions.z === 0) { + naturalDimensions.z === 0) { Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + - " is invalid or the model has not yet been loaded."); + " is invalid or the model has not yet been loaded."); } else { Entities.editEntity(selectionManager.selections[i], { dimensions: properties.naturalDimensions @@ -2089,6 +2089,8 @@ var PropertiesTool = function (opts) { Entities.reloadServerScripts(selectionManager.selections[i]); } } + } else if (data.action === "copySkyboxURLToAmbientURL") { + Window.notifyEditError("I DON'T KNOW HOW :("); } } else if (data.type === "propertiesPageReady") { updateSelections(true); diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index c914ec34d4..40ed8fe18f 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -536,6 +536,46 @@
+
+ + Background + + +
+
+ + Skybox + +
+
+ Skybox color +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + +
+
@@ -668,39 +708,7 @@ -
- - Background - - -
-
- - Skybox - -
-
- Skybox color -
-
-
-
-
-
-
- - -
-
- -
TextM diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 5d788726f6..390aeae96d 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -654,6 +654,8 @@ function loaded() { var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); // Ambient light + var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); + var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); @@ -1467,6 +1469,13 @@ function loaded() { elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); // Ambient light + elCopySkyboxURLToAmbientURL.addEventListener("click", function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "copySkyboxURLToAmbientURL" + })); + }); + var ambientLightModeChanged = createZoneComponentModeChangedFunction('ambientLightMode', elZoneAmbientLightModeInherit, elZoneAmbientLightModeDisabled, elZoneAmbientLightModeEnabled); From 9636a0b4500b732bb67eb1ff16cc214c60d3e8d8 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 28 Dec 2017 09:35:58 -0800 Subject: [PATCH 37/90] No idea... --- interface/resources/qml/js/Utils.jsc | Bin 6596 -> 6516 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc index ab20e996b9469915ac6a89901da175143e6b5024..dec1adb6f865f057bee2a149c2f75b38999f104d 100644 GIT binary patch delta 571 zcmX?N{KZJ8u*@VmC9xz?n1O+TiIbI~=hd;21V#o19aaVgHoo}aeohVMaEtRteXlX} zPSnv5oWKA9OJMZGRBf>h3=9k$j0_AQjXM|_7&w?fvKyC4F*0pon|z4Tjp>2#W-cZp zCRHm228J|-OokkWR0b;seK2$Zv)man8A=#D7)lwECdaY_Z{EVPlbO+Aa~{WiMn;3p zeq1)pX&Rp0Odj1137(xUDl>e$#eBPiph6iSp$RBL1t6gg6rl=`Py@11LjwZ?gJ)-p zO2z;G|Dh6-Kk!QUK+J6b`4%Ldfsh8fpVC&A*2iV;C72bXXY}mb_f{tI)LMb*EnX9J{G= zWhd%r2u=a1V_;xd0i!3TYBMn~Oze}}xIuuCiJN`$Ax1YQ1Ch;KOh!zK77PpwX$+YR zISi=`Rt)-J=nQ5#GvqU*Fr-dSWC>+b{ttoq>=ByP;tQR3ZUgVgXbl0wUpf+{t2c2;V_YW>%0zE-Eu7@8|bm+Qc$> zpP=$&9Ra?{EBJ*O|4!b+@6DtD6XfCh#4m8mV+SNeKiE%J5D=>uc>IY!?xDwySu;W4 zqkWnOf8L2Se!YkMZQ2YB3^3Ib9KaHv_~UNEgg!vje&Ua~_;Tt0|NqlG`0H-?7`^~G zXTc|afln{_kqls902%PXw;SwsDUeL_8--ny%Q>VsPY@_zWVD#fD3mA;4F`}LLFRzM z9^}`}l|l)OlOG5-Ggq4 From 64644fc980e57a11aabaadfacde89e3dbeb4ebc2 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 28 Dec 2017 14:31:59 -0800 Subject: [PATCH 38/90] WIP - skybox inheritance --- .../src/RenderableZoneEntityItem.cpp | 4 ++ .../src/RenderableZoneEntityItem.h | 5 +++ .../entities/src/EntityItemProperties.cpp | 42 ++++++++++++++++++- libraries/entities/src/EntityItemProperties.h | 5 +++ libraries/entities/src/EntityPropertyFlags.h | 1 + libraries/entities/src/ZoneEntityItem.cpp | 17 ++++++++ libraries/entities/src/ZoneEntityItem.h | 4 ++ scripts/system/html/entityProperties.html | 11 +++-- scripts/system/html/js/entityProperties.js | 35 ++++++++++++---- 9 files changed, 112 insertions(+), 12 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 6e6b87a6e5..c883bd631c 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -520,6 +520,10 @@ void ZoneEntityRenderer::setAmbientLightMode(ComponentMode mode) { _ambientLightMode = mode; } +void ZoneEntityRenderer::setSkyboxMode(ComponentMode mode) { + _skyboxMode = mode; +} + void ZoneEntityRenderer::setSkyboxColor(const glm::vec3& color) { editSkybox()->setColor(color); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index f753d39b97..f72aadf7b9 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -55,9 +55,12 @@ private: void setAmbientURL(const QString& ambientUrl); void setSkyboxURL(const QString& skyboxUrl); void setBackgroundMode(BackgroundMode mode); + void setHazeMode(ComponentMode mode); void setKeyLightMode(ComponentMode mode); void setAmbientLightMode(ComponentMode mode); + void setSkyboxMode(ComponentMode mode); + void setSkyboxColor(const glm::vec3& color); void setProceduralUserData(const QString& userData); @@ -87,9 +90,11 @@ private: const model::HazePointer _haze{ std::make_shared() }; BackgroundMode _backgroundMode{ BACKGROUND_MODE_INHERIT }; + ComponentMode _hazeMode { COMPONENT_MODE_INHERIT }; ComponentMode _keyLightMode { COMPONENT_MODE_INHERIT }; ComponentMode _ambientLightMode { COMPONENT_MODE_INHERIT }; + ComponentMode _skyboxMode { COMPONENT_MODE_INHERIT }; indexed_container::Index _sunIndex{ LightStage::INVALID_INDEX }; indexed_container::Index _shadowIndex{ LightStage::INVALID_INDEX }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index d13569e260..f5fce2929b 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -302,6 +302,35 @@ void EntityItemProperties::setAmbientLightModeFromString(const QString& ambientL } } +QString EntityItemProperties::getSkyboxModeAsString() const { + // return "enabled" if _skyboxMode is not valid + if (_skyboxMode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[_skyboxMode].second; + } else { + return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; + } +} + +QString EntityItemProperties::getSkyboxModeString(uint32_t mode) { + // return "enabled" if mode is not valid + if (mode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[mode].second; + } else { + return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; + } +} + +void EntityItemProperties::setSkyboxModeFromString(const QString& skyboxMode) { + auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { + return (pair.second == skyboxMode); + }); + + if (result != COMPONENT_MODES.end()) { + _skyboxMode = result->first; + _skyboxModeChanged = true; + } +} + EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; @@ -392,6 +421,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); CHECK_PROPERTY_CHANGE(PROP_KEY_LIGHT_MODE, keyLightMode); CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); + CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -631,6 +661,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_KEY_LIGHT_MODE, keyLightMode, getKeyLightModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AMBIENT_LIGHT_MODE, ambientLightMode, getAmbientLightModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); } // Web only @@ -820,6 +851,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(hazeMode, HazeMode); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(keyLightMode, KeyLightMode); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(ambientLightMode, AmbientLightMode); + COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(skyboxMode, SkyboxMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); @@ -980,6 +1012,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(hazeMode); COPY_PROPERTY_IF_CHANGED(keyLightMode); COPY_PROPERTY_IF_CHANGED(ambientLightMode); + COPY_PROPERTY_IF_CHANGED(skyboxMode); COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); @@ -1255,6 +1288,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t); @@ -1506,6 +1540,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode()); + APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1860,7 +1895,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); - } + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); + } if (properties.getType() == EntityTypes::PolyVox) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VOXEL_VOLUME_SIZE, glm::vec3, setVoxelVolumeSize); @@ -2449,6 +2485,10 @@ QList EntityItemProperties::listChangedProperties() { out += "ambientLightMode"; } + if (skyboxModeChanged()) { + out += "skyboxMode"; + } + if (voxelVolumeSizeChanged()) { out += "voxelVolumeSize"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 3de76e3777..336731c1d4 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -183,6 +183,7 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); @@ -253,6 +254,7 @@ public: static QString getHazeModeString(uint32_t mode); static QString getKeyLightModeString(uint32_t mode); static QString getAmbientLightModeString(uint32_t mode); + static QString getSkyboxModeString(uint32_t mode); public: float getMaxDimension() const { return glm::compMax(_dimensions); } @@ -481,9 +483,12 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, HazeMode, hazeMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, KeyLightMode, keyLightMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, SkyboxMode, skyboxMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 8317bad05f..d0cdf2d97c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -222,6 +222,7 @@ enum EntityPropertyList { PROP_KEY_LIGHT_MODE, PROP_AMBIENT_LIGHT_MODE, + PROP_SKYBOX_MODE, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index bab7d8c7b7..96dcc1bc32 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -78,6 +78,7 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightMode, getKeyLightMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ambientLightMode, getAmbientLightMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode); return properties; } @@ -131,6 +132,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged; @@ -199,6 +201,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); + READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); return bytesRead; } @@ -235,6 +238,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_KEY_LIGHT_MODE; requestedProperties += PROP_AMBIENT_LIGHT_MODE; + requestedProperties += PROP_SKYBOX_MODE; return requestedProperties; } @@ -276,6 +280,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)getAmbientLightMode()); + APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode()); } void ZoneEntityItem::debugDump() const { @@ -288,6 +293,7 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getHazeModeString(_hazeMode); qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getKeyLightModeString(_keyLightMode); qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getAmbientLightModeString(_ambientLightMode); + qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getSkyboxModeString(_skyboxMode); _keyLightProperties.debugDump(); _ambientLightProperties.debugDump(); @@ -396,3 +402,14 @@ void ZoneEntityItem::setAmbientLightMode(const uint32_t value) { uint32_t ZoneEntityItem::getAmbientLightMode() const { return _ambientLightMode; } + +void ZoneEntityItem::setSkyboxMode(const uint32_t value) { + if (value < COMPONENT_MODE_ITEM_COUNT) { + _skyboxMode = value; + _skyboxPropertiesChanged = true; + } +} + +uint32_t ZoneEntityItem::getSkyboxMode() const { + return _skyboxMode; +} diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 53c011b5ad..bdc5821da6 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -81,6 +81,9 @@ public: void setAmbientLightMode(uint32_t value); uint32_t getAmbientLightMode() const; + void setSkyboxMode(uint32_t value); + uint32_t getSkyboxMode() const; + SkyboxPropertyGroup getSkyboxProperties() const { return resultWithReadLock([&] { return _skyboxProperties; }); } const HazePropertyGroup& getHazeProperties() const { return _hazeProperties; } @@ -132,6 +135,7 @@ protected: uint32_t _hazeMode { COMPONENT_MODE_INHERIT }; uint32_t _keyLightMode { COMPONENT_MODE_INHERIT }; uint32_t _ambientLightMode { COMPONENT_MODE_INHERIT }; + uint32_t _skyboxMode { COMPONENT_MODE_INHERIT }; SkyboxPropertyGroup _skyboxProperties; HazePropertyGroup _hazeProperties; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 40ed8fe18f..85beab4098 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -540,6 +540,11 @@ Background +
+ Inherit + Off + On +
-
+
@@ -569,7 +569,7 @@
-
+
@@ -587,7 +587,7 @@
- + Haze diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index da9be64ef0..98e67cde05 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1104,8 +1104,15 @@ function loaded() { elZoneFilterURL.value = properties.filterURL; // Show/hide sections as required - showElements(document.getElementsByClassName('skybox-section'), + showElements(document.getElementsByClassName('skybox-section'), elZoneSkyboxModeEnabled.checked); + showElements(document.getElementsByClassName('keylight-section'), + elZoneKeyLightModeEnabled.checked); + showElements(document.getElementsByClassName('ambient-section'), + elZoneAmbientLightModeEnabled.checked); + showElements(document.getElementsByClassName('haze-section'), + elZoneHazeModeEnabled.checked); + } else if (properties.type === "PolyVox") { elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); From 87e896ab20a90a1d10c81ced65527364628ca5fe Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 29 Dec 2017 15:13:46 -0800 Subject: [PATCH 42/90] Zone inheritance ready for testing. --- .../entities-renderer/src/RenderableZoneEntityItem.cpp | 8 +++----- scripts/system/edit.js | 2 -- scripts/system/html/entityProperties.html | 3 +++ scripts/system/html/js/entityProperties.js | 7 +++---- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 8bf64db9b1..12e6d921b7 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -184,8 +184,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { // Just turned off, store previous value before changing _skyboxOnIndex = _backgroundIndex; _backgroundIndex = _stage->getSunOffLight(); - } - else if (_skyboxMode == COMPONENT_MODE_ENABLED && _skyboxOnIndex != NO_STORED_VALUE) { + } else if (_skyboxMode == COMPONENT_MODE_ENABLED && _skyboxOnIndex != NO_STORED_VALUE) { // Just turned on, restore previous value before clearing stored value _backgroundIndex = _skyboxOnIndex; _skyboxOnIndex = NO_STORED_VALUE; @@ -200,14 +199,13 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { // Just turned off, store previous value before changing _ambientOnIndex = _ambientIndex; _ambientIndex = _stage->getAmbientOffLight(); - } - else if (_ambientLightMode == COMPONENT_MODE_ENABLED && _ambientOnIndex != NO_STORED_VALUE) { + } else if (_ambientLightMode == COMPONENT_MODE_ENABLED && _ambientOnIndex != NO_STORED_VALUE) { // Just turned on, restore previous value before clearing stored value _ambientIndex = _ambientOnIndex; _ambientOnIndex = NO_STORED_VALUE; } - if (_ambientLightMode != COMPONENT_MODE_INHERIT && (_validAmbientTexture || _validSkyboxTexture)) { + if (_ambientLightMode != COMPONENT_MODE_INHERIT && (_validAmbientTexture)) { _stage->_currentFrame.pushAmbientLight(_ambientIndex); } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index ae3f88bd4e..44366aa61c 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2089,8 +2089,6 @@ var PropertiesTool = function (opts) { Entities.reloadServerScripts(selectionManager.selections[i]); } } - } else if (data.action === "copySkyboxURLToAmbientURL") { - Window.notifyEditError("I DON'T KNOW HOW :("); } } else if (data.type === "propertiesPageReady") { updateSelections(true); diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index ee2301b219..38d6a99f0c 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -564,8 +564,11 @@
+
+
+
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 98e67cde05..2e0af2d23d 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1492,10 +1492,9 @@ function loaded() { // Ambient light elCopySkyboxURLToAmbientURL.addEventListener("click", function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "copySkyboxURLToAmbientURL" - })); + document.getElementById("property-zone-key-ambient-url").value = properties.skybox.url; + properties.ambientLight.ambientURL = properties.skybox.url; + updateProperties(properties); }); var ambientLightModeChanged = createZoneComponentModeChangedFunction('ambientLightMode', From b6bbf1abe118ef61eb7c31e5ffd17320d0b33244 Mon Sep 17 00:00:00 2001 From: "nissim.hadar" Date: Fri, 29 Dec 2017 23:47:42 -0800 Subject: [PATCH 43/90] Added sun intensity modulation as a function of altitude. --- scripts/developer/sunModel.js | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/scripts/developer/sunModel.js b/scripts/developer/sunModel.js index dc0753cd73..2343e3da7f 100644 --- a/scripts/developer/sunModel.js +++ b/scripts/developer/sunModel.js @@ -8,7 +8,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Code is based on the NOAA model - see https://www.esrl.noaa.gov/gmd/grad/solcalc/ +// Sun angle is based on the NOAA model - see https://www.esrl.noaa.gov/gmd/grad/solcalc/ // (function() { // Utility functions for trig. calculations @@ -213,10 +213,10 @@ Math.cos(latitude) * Math.cos(theta_rads) * Math.cos(hourAngleRadians); csz = Math.min(1.0, Math.max(-1.0, csz)); - var zenith = toDegrees(Math.acos(csz)); - var azDenom = ( Math.cos(latitude) * Math.sin(toRadians(zenith))); + var zenith_degs = toDegrees(Math.acos(csz)); + var azDenom = ( Math.cos(latitude) * Math.sin(toRadians(zenith_degs))); if (Math.abs(azDenom) > 0.001) { - azRad = (( Math.sin(latitude) * Math.cos(toRadians(zenith)) ) - Math.sin(theta_rads)) / azDenom; + azRad = (( Math.sin(latitude) * Math.cos(toRadians(zenith_degs)) ) - Math.sin(theta_rads)) / azDenom; if (Math.abs(azRad) > 1.0) { if (azRad < 0.0) { azRad = -1.0; @@ -240,7 +240,7 @@ } // Atmospheric Refraction correction - var exoatmElevation = 90.0 - zenith; + var exoatmElevation = 90.0 - zenith_degs; if (exoatmElevation > 85.0) { var refractionCorrection = 0.0; } else { @@ -257,7 +257,7 @@ refractionCorrection = refractionCorrection / 3600.0; } - var solarZenith = zenith - refractionCorrection; + var solarZenith = zenith_degs - refractionCorrection; var solarAltitude_degs = 90.0 - solarZenith; // aka solar elevation // Convert to XYZ @@ -268,7 +268,24 @@ var zPos = Math.cos(solarAltitude) * Math.cos(solarAzimuth); var yPos = -Math.sin(solarAltitude); - Entities.editEntity(entityID, { keyLight : {direction: { x: xPos, y: yPos, z: zPos }}}); + // Compute intensity, modelling the atmosphere as a spherical shell + // The optical air mass ratio at zenith is 1.0, and around 38.0 at the horizon + // The ratio is limited between 1 and 38 + var EARTH_RADIUS_KM = 6371.0; + var ATMOSPHERE_THICKNESS_KM = 9.0; + var r = EARTH_RADIUS_KM / ATMOSPHERE_THICKNESS_KM; + + var opticalAirMassRatio = Math.sqrt(r * r * csz * csz + 2 * r + 1) - r * csz; + opticalAirMassRatio = Math.min(38.0, Math.max(1.0, opticalAirMassRatio)); + + Entities.editEntity( + entityID, { + keyLight : { + direction: { x: xPos, y: yPos, z: zPos }, + intensity: 1.0 / opticalAirMassRatio + } + } + ); }, COMPUTATION_CYCLE From b56c7535d22a62db5e79fc76921591968fb3cb11 Mon Sep 17 00:00:00 2001 From: "nissim.hadar" Date: Sat, 30 Dec 2017 12:25:58 -0800 Subject: [PATCH 44/90] Added legacy functionality (for zones that used Background "skybox") --- libraries/entities-renderer/src/RenderableZoneEntityItem.cpp | 5 +++-- libraries/entities/src/EntityItemProperties.cpp | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 672ad1ceec..a6c0b374fa 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -190,7 +190,8 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { _skyboxOnIndex = NO_STORED_VALUE; } - if (_skyboxMode != COMPONENT_MODE_INHERIT) { + // _backgroundMode is kept for legacy purposes + if (_skyboxMode != COMPONENT_MODE_INHERIT || _backgroundMode != BACKGROUND_MODE_INHERIT) { _backgroundStage->_currentFrame.pushBackground(_backgroundIndex); } @@ -205,7 +206,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { _ambientOnIndex = NO_STORED_VALUE; } - if (_ambientLightMode != COMPONENT_MODE_INHERIT && (_validAmbientTexture)) { + if (_ambientLightMode != COMPONENT_MODE_INHERIT && _validAmbientTexture) { _stage->_currentFrame.pushAmbientLight(_ambientIndex); } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3a38e4acd7..d5a58c9580 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -2130,6 +2130,7 @@ void EntityItemProperties::markAllChanged() { _keyLight.markAllChanged(); _ambientLight.markAllChanged(); + _skybox.markAllChanged(); _backgroundModeChanged = true; _hazeModeChanged = true; From fb59da5fb34842e933dfd1d2d903c6a484031cf5 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Sat, 30 Dec 2017 18:19:00 -0800 Subject: [PATCH 45/90] Reverted to old version of auto-tester. --- tools/auto-tester/CMakeLists.txt | 41 +++-- tools/auto-tester/src/ImageComparer.cpp | 6 +- tools/auto-tester/src/Test.cpp | 176 ++++---------------- tools/auto-tester/src/Test.h | 20 +-- tools/auto-tester/src/common.h | 5 - tools/auto-tester/src/ui/AutoTester.cpp | 8 +- tools/auto-tester/src/ui/AutoTester.h | 6 +- tools/auto-tester/src/ui/AutoTester.ui | 65 ++------ tools/auto-tester/src/ui/MismatchWindow.cpp | 56 +------ tools/auto-tester/src/ui/MismatchWindow.h | 5 - tools/auto-tester/src/ui/MismatchWindow.ui | 103 ++++-------- 11 files changed, 115 insertions(+), 376 deletions(-) diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index fe91c89352..e5f2c1fb97 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -1,24 +1,24 @@ -set (TARGET_NAME auto-tester) +set(TARGET_NAME auto-tester) project(${TARGET_NAME}) # Automatically run UIC and MOC. This replaces the older WRAP macros -SET (CMAKE_AUTOUIC ON) -SET (CMAKE_AUTOMOC ON) +SET(CMAKE_AUTOUIC ON) +SET(CMAKE_AUTOMOC ON) -setup_hifi_project (Core Widgets) -link_hifi_libraries () +setup_hifi_project(Core Widgets) +link_hifi_libraries() # FIX: Qt was built with -reduce-relocations if (Qt5_POSITION_INDEPENDENT_CODE) - SET (CMAKE_POSITION_INDEPENDENT_CODE ON) + SET(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() # Qt includes -include_directories (${CMAKE_CURRENT_SOURCE_DIR}) -include_directories (${Qt5Core_INCLUDE_DIRS}) -include_directories (${Qt5Widgets_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${Qt5Core_INCLUDE_DIRS}) +include_directories(${Qt5Widgets_INCLUDE_DIRS}) -set (QT_LIBRARIES Qt5::Core Qt5::Widgets) +set(QT_LIBRARIES Qt5::Core Qt5::Widgets) # Find all sources files file (GLOB_RECURSE SOURCES src/*.cpp) @@ -27,24 +27,21 @@ file (GLOB_RECURSE UIS src/ui/*.ui) if (WIN32) # Do not show Console - set_property (TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) + set_property(TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) endif() -add_executable (PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS}) - -target_zlib() -add_dependency_external_projects (quazip) -find_package (QuaZip REQUIRED) -target_include_directories( ${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) +add_executable(PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS}) -target_link_libraries (PROJECT_NAME ${QT_LIBRARIES}) +target_link_libraries(PROJECT_NAME ${QT_LIBRARIES}) -package_libraries_for_deployment() +# Copy required dll's. +add_custom_command(TARGET auto-tester POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ +) if (WIN32) - add_paths_to_fixup_libs (${QUAZIP_DLL_PATH}) - find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) if (NOT WINDEPLOYQT_COMMAND) diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index a80978e564..121c98e16e 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -8,7 +8,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "ImageComparer.h" -#include "common.h" #include @@ -27,6 +26,11 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co const double c1 = pow((K1 * L), 2); const double c2 = pow((K2 * L), 2); + // Coefficients for luminosity calculation + const double R_Y = 0.212655f; + const double G_Y = 0.715158f; + const double B_Y = 0.072187f; + // First go over all full 8x8 blocks // This is done in 3 loops // 1) Read the pixels into a linear array (an optimization) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 8bad468afa..8cb36fcfca 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -13,9 +13,6 @@ #include #include -#include -#include - Test::Test() { snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.+-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); @@ -24,51 +21,10 @@ Test::Test() { mismatchWindow.setModal(true); } -bool Test::createTestResultsFolderPathIfNeeded(QString directory) { - // The test results folder is located in the root of the tests (i.e. for recursive test evaluation) - if (testResultsFolderPath == "") { - testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER; - QDir testResultsFolder(testResultsFolderPath); - - if (testResultsFolder.exists()) { - testResultsFolder.removeRecursively(); - } - - // Create a new test results folder - return QDir().mkdir(testResultsFolderPath); - } else { - return true; - } -} - -void Test::zipAndDeleteTestResultsFolder() { - QString zippedResultsFileName { testResultsFolderPath + ".zip" }; - QFileInfo fileInfo(zippedResultsFileName); - if (!fileInfo.exists()) { - QFile::remove(zippedResultsFileName); - } - - QDir testResultsFolder(testResultsFolderPath); - if (!testResultsFolder.isEmpty()) { - JlCompress::compressDir(testResultsFolderPath + ".zip", testResultsFolderPath); - } - - testResultsFolder.removeRecursively(); - - //In all cases, for the next evaluation - testResultsFolderPath = ""; - index = 1; -} - -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar) { - progressBar->setMinimum(0); - progressBar->setMaximum(expectedImages.length() - 1); - progressBar->setValue(0); - progressBar->setVisible(true); - +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages) { // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. - const double THRESHOLD { 0.999 }; + const double THRESHOLD{ 0.999 }; bool success{ true }; bool keepOn{ true }; for (int i = 0; keepOn && i < expectedImages.length(); ++i) { @@ -89,107 +45,42 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage } if (similarityIndex < THRESHOLD) { - TestFailure testFailure = TestFailure{ + mismatchWindow.setTestFailure(TestFailure{ (float)similarityIndex, expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /) QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image - }; + }); - mismatchWindow.setTestFailure(testFailure); + mismatchWindow.exec(); - if (!interactiveMode) { - appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); - success = false; - } else { - mismatchWindow.exec(); - - switch (mismatchWindow.getUserResponse()) { - case USER_RESPONSE_PASS: - break; - case USE_RESPONSE_FAIL: - appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); - success = false; - break; - case USER_RESPONSE_ABORT: - keepOn = false; - success = false; - break; - default: - assert(false); - break; - } + switch (mismatchWindow.getUserResponse()) { + case USER_RESPONSE_PASS: + break; + case USE_RESPONSE_FAIL: + success = false; + break; + case USER_RESPONSE_ABORT: + keepOn = false; + success = false; + break; + default: + assert(false); + break; } } - - progressBar->setValue(i); } - progressBar->setVisible(false); return success; } -void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { - if (!QDir().exists(testResultsFolderPath)) { - messageBox.critical(0, "Internal error", "Folder " + testResultsFolderPath + " not found"); - exit(-1); - } - - QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; - if (!QDir().mkdir(failureFolderPath)) { - messageBox.critical(0, "Internal error", "Failed to create folder " + failureFolderPath); - exit(-1); - } - ++index; - - QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); - if (!descriptionFile.open(QIODevice::ReadWrite)) { - messageBox.critical(0, "Internal error", "Failed to create file " + TEST_RESULTS_FILENAME); - exit(-1); - } - - // Create text file describing the failure - QTextStream stream(&descriptionFile); - stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/' - stream << "Expected image was " << testFailure._expectedImageFilename << endl; - stream << "Actual image was " << testFailure._actualImageFilename << endl; - stream << "Similarity index was " << testFailure._error << endl; - - descriptionFile.close(); - - // Copy expected and actual images, and save the difference image - QString sourceFile; - QString destinationFile; - - sourceFile = testFailure._pathname + testFailure._expectedImageFilename; - destinationFile = failureFolderPath + "/" + "Expected Image.jpg"; - if (!QFile::copy(sourceFile, destinationFile)) { - messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); - exit(-1); - } - - sourceFile = testFailure._pathname + testFailure._actualImageFilename; - destinationFile = failureFolderPath + "/" + "Actual Image.jpg"; - if (!QFile::copy(sourceFile, destinationFile)) { - messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); - exit(-1); - } - - comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg"); -} - -void Test::evaluateTests(bool interactiveMode, QProgressBar* progressBar) { +void Test::evaluateTests() { // Get list of JPEG images in folder, sorted by name QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); if (pathToImageDirectory == "") { return; } - // Leave if test results folder could not be created - if (!createTestResultsFolderPathIfNeeded(pathToImageDirectory)) { - return; - } - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); // Separate images into two lists. The first is the expected images, the second is the test results @@ -216,32 +107,25 @@ void Test::evaluateTests(bool interactiveMode, QProgressBar* progressBar) { exit(-1); } - bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode, progressBar); + bool success = compareImageLists(expectedImages, resultImages); if (success) { messageBox.information(0, "Success", "All images are as expected"); } else { messageBox.information(0, "Failure", "One or more images are not as expected"); } - - zipAndDeleteTestResultsFolder(); } // Two criteria are used to decide if a folder contains valid test results. // 1) a 'test'js' file exists in the folder // 2) the folder has the same number of actual and expected images -void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar) { +void Test::evaluateTestsRecursively() { // Select folder to start recursing from QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); if (topLevelDirectory == "") { return; } - // Leave if test results folder could not be created - if (!createTestResultsFolderPathIfNeeded(topLevelDirectory)) { - return; - } - bool success{ true }; QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { @@ -251,7 +135,8 @@ void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progress continue; } - const QString testPathname{ directory + "/" + TEST_FILENAME }; + // + const QString testPathname{ directory + "/" + testFilename }; QFileInfo fileInfo(testPathname); if (!fileInfo.exists()) { // Folder does not contain 'test.js' @@ -279,7 +164,7 @@ void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progress } // Set success to false if any test has failed - success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode, progressBar); + success &= compareImageLists(expectedImages, resultImages); } if (success) { @@ -287,8 +172,6 @@ void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progress } else { messageBox.information(0, "Failure", "One or more images are not as expected"); } - - zipAndDeleteTestResultsFolder(); } void Test::importTest(QTextStream& textStream, const QString& testPathname, int testNumber) { @@ -308,8 +191,7 @@ void Test::createRecursiveScript() { if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { messageBox.critical(0, "Internal Error", - "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\"" - ); + "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\""); exit(-1); } @@ -324,7 +206,7 @@ void Test::createRecursiveScript() { QVector testPathnames; // First test if top-level folder has a test.js file - const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; + const QString testPathname{ topLevelDirectory + "/" + testFilename }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -342,7 +224,7 @@ void Test::createRecursiveScript() { continue; } - const QString testPathname{ directory + "/" + TEST_FILENAME }; + const QString testPathname{ directory + "/" + testFilename }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -382,7 +264,7 @@ void Test::createRecursiveScript() { // The script produced will look as follows: // if (test1HasNotStarted) { // test1HasNotStarted = false; - // test1.test("auto"); + // test1.test(); // print("******started test 1******"); // } // | @@ -405,7 +287,7 @@ void Test::createRecursiveScript() { textStream << tab << tab << "if (test" << i - 1 << ".complete && test" << i << "HasNotStarted) {" << endl; } textStream << tab << tab << tab << "test" << i << "HasNotStarted = false;" << endl; - textStream << tab << tab << tab << "test" << i << "." << testFunction << "(\"auto\");" << endl; + textStream << tab << tab << tab << "test" << i << "." << testFunction << "();" << endl; textStream << tab << tab << tab << "print(\"******started test " << i << "******\");" << endl; textStream << tab << tab << "}" << endl << endl; diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 37827e9e0b..1f7b1e92a7 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -15,7 +15,6 @@ #include #include #include -#include #include "ImageComparer.h" #include "ui/MismatchWindow.h" @@ -24,13 +23,11 @@ class Test { public: Test(); - void evaluateTests(bool interactiveMode, QProgressBar* progressBar); - void evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar); + void evaluateTests(); + void evaluateTestsRecursively(); void createRecursiveScript(); void createTest(); - bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar); - QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory); bool isInSnapshotFilenameFormat(QString filename); @@ -38,15 +35,8 @@ public: void importTest(QTextStream& textStream, const QString& testPathname, int testNumber); - void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); - - bool createTestResultsFolderPathIfNeeded(QString directory); - void zipAndDeleteTestResultsFolder(); - private: - const QString TEST_FILENAME { "test.js" }; - const QString TEST_RESULTS_FOLDER { "TestResults" }; - const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; + const QString testFilename{ "test.js" }; QMessageBox messageBox; @@ -59,9 +49,7 @@ private: ImageComparer imageComparer; - - QString testResultsFolderPath { "" }; - int index { 1 }; + bool compareImageLists(QStringList expectedImages, QStringList resultImages); }; #endif // hifi_test_h diff --git a/tools/auto-tester/src/common.h b/tools/auto-tester/src/common.h index 0c21d79b33..126177358f 100644 --- a/tools/auto-tester/src/common.h +++ b/tools/auto-tester/src/common.h @@ -34,9 +34,4 @@ enum UserResponse { USER_RESPONSE_ABORT }; -// Coefficients for luminosity calculation -const double R_Y = 0.212655f; -const double G_Y = 0.715158f; -const double B_Y = 0.072187f; - #endif // hifi_common_h diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 1f1283b98b..105baddb92 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -12,18 +12,14 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); - - ui.checkBoxInteractiveMode->setChecked(true); - - ui.progressBar->setVisible(false); } void AutoTester::on_evaluateTestsButton_clicked() { - test.evaluateTests(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); + test.evaluateTests(); } void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { - test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); + test.evaluateTestsRecursively(); } void AutoTester::on_createRecursiveScriptButton_clicked() { diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 99af639582..acfea32ba1 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -22,9 +22,9 @@ public: AutoTester(QWidget *parent = Q_NULLPTR); private slots: - void on_evaluateTestsButton_clicked(); - void on_evaluateTestsRecursivelyButton_clicked(); - void on_createRecursiveScriptButton_clicked(); +void on_evaluateTestsButton_clicked(); +void on_evaluateTestsRecursivelyButton_clicked(); +void on_createRecursiveScriptButton_clicked(); void on_createTestButton_clicked(); void on_closeButton_clicked(); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 544141975f..7032ef9710 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 607 - 395 + 286 + 470
@@ -17,9 +17,9 @@ - 190 - 300 - 220 + 60 + 360 + 160 40 @@ -30,9 +30,9 @@ - 360 - 130 - 220 + 60 + 270 + 160 40 @@ -43,9 +43,9 @@ - 20 - 75 - 220 + 60 + 20 + 160 40 @@ -56,9 +56,9 @@ - 360 - 75 - 220 + 60 + 210 + 160 40 @@ -69,9 +69,9 @@ - 20 - 130 - 220 + 60 + 75 + 160 40 @@ -79,42 +79,13 @@ Evaluate Tests Recursively
- - - - 23 - 40 - 131 - 20 - - - - <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> - - - Interactive Mode - - - - - - 20 - 190 - 255 - 23 - - - - 24 - - 0 0 - 607 + 286 21 diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index fe22412522..07664a1667 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -11,48 +11,11 @@ #include -#include - MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { setupUi(this); expectedImage->setScaledContents(true); resultImage->setScaledContents(true); - diffImage->setScaledContents(true); -} - -QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) { - // This is an optimization, as QImage.setPixel() is embarrassingly slow - unsigned char* buffer = new unsigned char[expectedImage.height() * expectedImage.width() * 3]; - - // loop over each pixel - for (int y = 0; y < expectedImage.height(); ++y) { - for (int x = 0; x < expectedImage.width(); ++x) { - QRgb pixelP = expectedImage.pixel(QPoint(x, y)); - QRgb pixelQ = resultImage.pixel(QPoint(x, y)); - - // Convert to luminance - double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); - double q = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); - - // The intensity value is modified to increase the brightness of the displayed image - double absoluteDifference = fabs(p - q) / 255.0; - double modifiedDifference = sqrt(absoluteDifference); - - int difference = (int)(modifiedDifference * 255.0); - - buffer[3 * (x + y * expectedImage.width()) + 0] = difference; - buffer[3 * (x + y * expectedImage.width()) + 1] = difference; - buffer[3 * (x + y * expectedImage.width()) + 2] = difference; - } - } - - QImage diffImage(buffer, expectedImage.width(), expectedImage.height(), QImage::Format_RGB888); - QPixmap resultPixmap = QPixmap::fromImage(diffImage); - - delete[] buffer; - - return resultPixmap; } void MismatchWindow::setTestFailure(TestFailure testFailure) { @@ -61,19 +24,10 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) { imagePath->setText("Path to test: " + testFailure._pathname); expectedFilename->setText(testFailure._expectedImageFilename); + expectedImage->setPixmap(QPixmap(testFailure._pathname + testFailure._expectedImageFilename)); + resultFilename->setText(testFailure._actualImageFilename); - - QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename); - QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename); - - diffPixmap = computeDiffPixmap( - QImage(testFailure._pathname + testFailure._expectedImageFilename), - QImage(testFailure._pathname + testFailure._actualImageFilename) - ); - - expectedImage->setPixmap(expectedPixmap); - resultImage->setPixmap(actualPixmap); - diffImage->setPixmap(diffPixmap); + resultImage->setPixmap(QPixmap(testFailure._pathname + testFailure._actualImageFilename)); } void MismatchWindow::on_passTestButton_clicked() { @@ -90,7 +44,3 @@ void MismatchWindow::on_abortTestsButton_clicked() { _userResponse = USER_RESPONSE_ABORT; close(); } - -QPixmap MismatchWindow::getComparisonImage() { - return diffPixmap; -} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h index ad8be16580..7c72b7b0b7 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.h +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -25,9 +25,6 @@ public: UserResponse getUserResponse() { return _userResponse; } - QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); - QPixmap getComparisonImage(); - private slots: void on_passTestButton_clicked(); void on_failTestButton_clicked(); @@ -35,8 +32,6 @@ private slots: private: UserResponse _userResponse{ USER_RESPONSE_INVALID }; - - QPixmap diffPixmap; }; diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui index 5ecf966df5..cab6c61e1c 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.ui +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -6,8 +6,8 @@ 0 0 - 1782 - 942 + 1585 + 694
@@ -16,10 +16,10 @@ - 10 - 25 - 800 - 450 + 20 + 170 + 720 + 362 @@ -29,41 +29,28 @@ - 900 - 25 - 800 - 450 + 760 + 170 + 720 + 362 result image - - - - 540 - 480 - 800 - 450 - - - - diff image - - - 60 - 660 - 480 + 760 + 90 + 800 28 - 12 + 16 @@ -73,15 +60,15 @@ - 60 - 630 - 480 + 40 + 90 + 700 28 - 12 + 16 @@ -91,15 +78,15 @@ - 20 - 600 + 40 + 30 1200 28 - 12 + 16 @@ -110,7 +97,7 @@ 30 - 790 + 600 75 23 @@ -122,8 +109,8 @@ - 120 - 790 + 330 + 600 75 23 @@ -135,60 +122,34 @@ - 210 - 790 - 121 + 630 + 600 + 75 23 - Abort current test + Abort Tests - 30 - 850 - 500 + 810 + 600 + 720 28 - 12 + 16 similarity - - - - 30 - 5 - 151 - 16 - - - - Expected Image - - - - - - 930 - 5 - 151 - 16 - - - - Actual Image - - From 0764e80996c7bf30f3ba08527890fcd9bbd3263d Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Sat, 30 Dec 2017 18:19:42 -0800 Subject: [PATCH 46/90] Corrected behaviour of skybox modes. --- libraries/entities-renderer/src/RenderableZoneEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index a6c0b374fa..999bcf45bd 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -183,7 +183,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { if (_skyboxMode == COMPONENT_MODE_DISABLED && _skyboxOnIndex == NO_STORED_VALUE) { // Just turned off, store previous value before changing _skyboxOnIndex = _backgroundIndex; - _backgroundIndex = _stage->getSunOffLight(); + _backgroundIndex = INVALID_INDEX; } else if (_skyboxMode == COMPONENT_MODE_ENABLED && _skyboxOnIndex != NO_STORED_VALUE) { // Just turned on, restore previous value before clearing stored value _backgroundIndex = _skyboxOnIndex; From dad3a0d3408320d2a7525a3cde92546e8a0f5a2d Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 2 Jan 2018 09:08:05 -0800 Subject: [PATCH 47/90] Fixed skybox inheritance. --- libraries/entities-renderer/src/RenderableZoneEntityItem.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 999bcf45bd..0d975759b4 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -145,10 +145,8 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { updateSkyboxMap(); if (_needBackgroundUpdate) { - if (BackgroundStage::isIndexInvalid(_backgroundIndex)) { + if (_skyboxMode == COMPONENT_MODE_ENABLED && BackgroundStage::isIndexInvalid(_backgroundIndex)) { _backgroundIndex = _backgroundStage->addBackground(_background); - } else { - } _needBackgroundUpdate = false; } From 22c5890812f926618e644aed0e3306ed32dc2570 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 2 Jan 2018 09:23:38 -0800 Subject: [PATCH 48/90] Rebased. --- tools/auto-tester/CMakeLists.txt | 44 ++-- tools/auto-tester/src/ImageComparer.cpp | 6 +- tools/auto-tester/src/Test.cpp | 210 +++++++++++++++++--- tools/auto-tester/src/Test.h | 21 +- tools/auto-tester/src/common.h | 5 + tools/auto-tester/src/ui/AutoTester.cpp | 12 +- tools/auto-tester/src/ui/AutoTester.h | 7 +- tools/auto-tester/src/ui/AutoTester.ui | 78 ++++++-- tools/auto-tester/src/ui/MismatchWindow.cpp | 56 +++++- tools/auto-tester/src/ui/MismatchWindow.h | 5 + tools/auto-tester/src/ui/MismatchWindow.ui | 103 +++++++--- 11 files changed, 426 insertions(+), 121 deletions(-) diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index e5f2c1fb97..a875f5676a 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -1,47 +1,41 @@ -set(TARGET_NAME auto-tester) +set (TARGET_NAME auto-tester) project(${TARGET_NAME}) # Automatically run UIC and MOC. This replaces the older WRAP macros -SET(CMAKE_AUTOUIC ON) -SET(CMAKE_AUTOMOC ON) +SET (CMAKE_AUTOUIC ON) +SET (CMAKE_AUTOMOC ON) -setup_hifi_project(Core Widgets) -link_hifi_libraries() +setup_hifi_project (Core Widgets) +link_hifi_libraries () # FIX: Qt was built with -reduce-relocations if (Qt5_POSITION_INDEPENDENT_CODE) - SET(CMAKE_POSITION_INDEPENDENT_CODE ON) + SET (CMAKE_POSITION_INDEPENDENT_CODE ON) endif() # Qt includes -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${Qt5Core_INCLUDE_DIRS}) -include_directories(${Qt5Widgets_INCLUDE_DIRS}) +include_directories (${CMAKE_CURRENT_SOURCE_DIR}) +include_directories (${Qt5Core_INCLUDE_DIRS}) +include_directories (${Qt5Widgets_INCLUDE_DIRS}) -set(QT_LIBRARIES Qt5::Core Qt5::Widgets) - -# Find all sources files -file (GLOB_RECURSE SOURCES src/*.cpp) -file (GLOB_RECURSE HEADERS src/*.h) -file (GLOB_RECURSE UIS src/ui/*.ui) +set (QT_LIBRARIES Qt5::Core Qt5::Widgets) if (WIN32) # Do not show Console - set_property(TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) + set_property (TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) endif() -add_executable(PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS}) +target_zlib() +add_dependency_external_projects (quazip) +find_package (QuaZip REQUIRED) +target_include_directories( ${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) -target_link_libraries(PROJECT_NAME ${QT_LIBRARIES}) - -# Copy required dll's. -add_custom_command(TARGET auto-tester POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ -) +package_libraries_for_deployment() if (WIN32) + add_paths_to_fixup_libs (${QUAZIP_DLL_PATH}) + find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) if (NOT WINDEPLOYQT_COMMAND) diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index 121c98e16e..a80978e564 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -8,6 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "ImageComparer.h" +#include "common.h" #include @@ -26,11 +27,6 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co const double c1 = pow((K1 * L), 2); const double c2 = pow((K2 * L), 2); - // Coefficients for luminosity calculation - const double R_Y = 0.212655f; - const double G_Y = 0.715158f; - const double B_Y = 0.072187f; - // First go over all full 8x8 blocks // This is done in 3 loops // 1) Read the pixels into a linear array (an optimization) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 8cb36fcfca..6294829655 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -13,6 +13,9 @@ #include #include +#include +#include + Test::Test() { snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.+-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); @@ -21,10 +24,51 @@ Test::Test() { mismatchWindow.setModal(true); } -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages) { +bool Test::createTestResultsFolderPathIfNeeded(QString directory) { + // The test results folder is located in the root of the tests (i.e. for recursive test evaluation) + if (testResultsFolderPath == "") { + testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER; + QDir testResultsFolder(testResultsFolderPath); + + if (testResultsFolder.exists()) { + testResultsFolder.removeRecursively(); + } + + // Create a new test results folder + return QDir().mkdir(testResultsFolderPath); + } else { + return true; + } +} + +void Test::zipAndDeleteTestResultsFolder() { + QString zippedResultsFileName { testResultsFolderPath + ".zip" }; + QFileInfo fileInfo(zippedResultsFileName); + if (!fileInfo.exists()) { + QFile::remove(zippedResultsFileName); + } + + QDir testResultsFolder(testResultsFolderPath); + if (!testResultsFolder.isEmpty()) { + JlCompress::compressDir(testResultsFolderPath + ".zip", testResultsFolderPath); + } + + testResultsFolder.removeRecursively(); + + //In all cases, for the next evaluation + testResultsFolderPath = ""; + index = 1; +} + +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar) { + progressBar->setMinimum(0); + progressBar->setMaximum(expectedImages.length() - 1); + progressBar->setValue(0); + progressBar->setVisible(true); + // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. - const double THRESHOLD{ 0.999 }; + const double THRESHOLD { 0.999 }; bool success{ true }; bool keepOn{ true }; for (int i = 0; keepOn && i < expectedImages.length(); ++i) { @@ -45,42 +89,107 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage } if (similarityIndex < THRESHOLD) { - mismatchWindow.setTestFailure(TestFailure{ + TestFailure testFailure = TestFailure{ (float)similarityIndex, expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /) QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image - }); + }; - mismatchWindow.exec(); + mismatchWindow.setTestFailure(testFailure); - switch (mismatchWindow.getUserResponse()) { - case USER_RESPONSE_PASS: - break; - case USE_RESPONSE_FAIL: - success = false; - break; - case USER_RESPONSE_ABORT: - keepOn = false; - success = false; - break; - default: - assert(false); - break; + if (!interactiveMode) { + appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); + success = false; + } else { + mismatchWindow.exec(); + + switch (mismatchWindow.getUserResponse()) { + case USER_RESPONSE_PASS: + break; + case USE_RESPONSE_FAIL: + appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); + success = false; + break; + case USER_RESPONSE_ABORT: + keepOn = false; + success = false; + break; + default: + assert(false); + break; + } } } + + progressBar->setValue(i); } + progressBar->setVisible(false); return success; } -void Test::evaluateTests() { +void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { + if (!QDir().exists(testResultsFolderPath)) { + messageBox.critical(0, "Internal error", "Folder " + testResultsFolderPath + " not found"); + exit(-1); + } + + QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; + if (!QDir().mkdir(failureFolderPath)) { + messageBox.critical(0, "Internal error", "Failed to create folder " + failureFolderPath); + exit(-1); + } + ++index; + + QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); + if (!descriptionFile.open(QIODevice::ReadWrite)) { + messageBox.critical(0, "Internal error", "Failed to create file " + TEST_RESULTS_FILENAME); + exit(-1); + } + + // Create text file describing the failure + QTextStream stream(&descriptionFile); + stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/' + stream << "Expected image was " << testFailure._expectedImageFilename << endl; + stream << "Actual image was " << testFailure._actualImageFilename << endl; + stream << "Similarity index was " << testFailure._error << endl; + + descriptionFile.close(); + + // Copy expected and actual images, and save the difference image + QString sourceFile; + QString destinationFile; + + sourceFile = testFailure._pathname + testFailure._expectedImageFilename; + destinationFile = failureFolderPath + "/" + "Expected Image.jpg"; + if (!QFile::copy(sourceFile, destinationFile)) { + messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + + sourceFile = testFailure._pathname + testFailure._actualImageFilename; + destinationFile = failureFolderPath + "/" + "Actual Image.jpg"; + if (!QFile::copy(sourceFile, destinationFile)) { + messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + + comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg"); +} + +void Test::evaluateTests(bool interactiveMode, QProgressBar* progressBar) { // Get list of JPEG images in folder, sorted by name QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); if (pathToImageDirectory == "") { return; } + // Leave if test results folder could not be created + if (!createTestResultsFolderPathIfNeeded(pathToImageDirectory)) { + return; + } + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); // Separate images into two lists. The first is the expected images, the second is the test results @@ -107,25 +216,32 @@ void Test::evaluateTests() { exit(-1); } - bool success = compareImageLists(expectedImages, resultImages); + bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode, progressBar); if (success) { messageBox.information(0, "Success", "All images are as expected"); } else { messageBox.information(0, "Failure", "One or more images are not as expected"); } + + zipAndDeleteTestResultsFolder(); } // Two criteria are used to decide if a folder contains valid test results. // 1) a 'test'js' file exists in the folder // 2) the folder has the same number of actual and expected images -void Test::evaluateTestsRecursively() { +void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar) { // Select folder to start recursing from QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); if (topLevelDirectory == "") { return; } + // Leave if test results folder could not be created + if (!createTestResultsFolderPathIfNeeded(topLevelDirectory)) { + return; + } + bool success{ true }; QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { @@ -135,8 +251,7 @@ void Test::evaluateTestsRecursively() { continue; } - // - const QString testPathname{ directory + "/" + testFilename }; + const QString testPathname{ directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (!fileInfo.exists()) { // Folder does not contain 'test.js' @@ -164,7 +279,7 @@ void Test::evaluateTestsRecursively() { } // Set success to false if any test has failed - success &= compareImageLists(expectedImages, resultImages); + success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode, progressBar); } if (success) { @@ -172,6 +287,8 @@ void Test::evaluateTestsRecursively() { } else { messageBox.information(0, "Failure", "One or more images are not as expected"); } + + zipAndDeleteTestResultsFolder(); } void Test::importTest(QTextStream& textStream, const QString& testPathname, int testNumber) { @@ -191,7 +308,8 @@ void Test::createRecursiveScript() { if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { messageBox.critical(0, "Internal Error", - "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\""); + "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\"" + ); exit(-1); } @@ -206,7 +324,7 @@ void Test::createRecursiveScript() { QVector testPathnames; // First test if top-level folder has a test.js file - const QString testPathname{ topLevelDirectory + "/" + testFilename }; + const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -224,7 +342,7 @@ void Test::createRecursiveScript() { continue; } - const QString testPathname{ directory + "/" + testFilename }; + const QString testPathname{ directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -264,7 +382,7 @@ void Test::createRecursiveScript() { // The script produced will look as follows: // if (test1HasNotStarted) { // test1HasNotStarted = false; - // test1.test(); + // test1.test("auto"); // print("******started test 1******"); // } // | @@ -287,7 +405,7 @@ void Test::createRecursiveScript() { textStream << tab << tab << "if (test" << i - 1 << ".complete && test" << i << "HasNotStarted) {" << endl; } textStream << tab << tab << tab << "test" << i << "HasNotStarted = false;" << endl; - textStream << tab << tab << tab << "test" << i << "." << testFunction << "();" << endl; + textStream << tab << tab << tab << "test" << i << "." << testFunction << "(\"auto\");" << endl; textStream << tab << tab << tab << "print(\"******started test " << i << "******\");" << endl; textStream << tab << tab << "}" << endl << endl; @@ -366,6 +484,39 @@ void Test::createTest() { messageBox.information(0, "Success", "Test images have been created"); } +void Test::deleteOldSnapshots() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select root folder for snapshot deletion", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + // Recurse over folders + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + if (directory[directory.length() - 1] == '.') { + // ignore '.', '..' directories + continue; + } + + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory); + + // Delete any file that is a snapshot (NOT the Expected Images) + QStringList expectedImages; + QStringList resultImages; + foreach(QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = directory + "/" + currentFilename; + if (isInSnapshotFilenameFormat(currentFilename)) { + if (!QFile::remove(fullCurrentFilename)) { + messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nSnapshot deletion aborted"); + exit(-1); + } + } + } + } +} + QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory) { imageDirectory = QDir(pathToImageDirectory); QStringList nameFilters; @@ -374,6 +525,7 @@ QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirect return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); } +// Use regular expressions to check if files are in specific format bool Test::isInSnapshotFilenameFormat(QString filename) { return (snapshotFilenameFormat.match(filename).hasMatch()); } diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 1f7b1e92a7..f3625b1fa3 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "ImageComparer.h" #include "ui/MismatchWindow.h" @@ -23,10 +24,13 @@ class Test { public: Test(); - void evaluateTests(); - void evaluateTestsRecursively(); + void evaluateTests(bool interactiveMode, QProgressBar* progressBar); + void evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar); void createRecursiveScript(); void createTest(); + void deleteOldSnapshots(); + + bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar); QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory); @@ -35,8 +39,15 @@ public: void importTest(QTextStream& textStream, const QString& testPathname, int testNumber); + void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); + + bool createTestResultsFolderPathIfNeeded(QString directory); + void zipAndDeleteTestResultsFolder(); + private: - const QString testFilename{ "test.js" }; + const QString TEST_FILENAME { "test.js" }; + const QString TEST_RESULTS_FOLDER { "TestResults" }; + const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; QMessageBox messageBox; @@ -49,7 +60,9 @@ private: ImageComparer imageComparer; - bool compareImageLists(QStringList expectedImages, QStringList resultImages); + + QString testResultsFolderPath { "" }; + int index { 1 }; }; #endif // hifi_test_h diff --git a/tools/auto-tester/src/common.h b/tools/auto-tester/src/common.h index 126177358f..0c21d79b33 100644 --- a/tools/auto-tester/src/common.h +++ b/tools/auto-tester/src/common.h @@ -34,4 +34,9 @@ enum UserResponse { USER_RESPONSE_ABORT }; +// Coefficients for luminosity calculation +const double R_Y = 0.212655f; +const double G_Y = 0.715158f; +const double B_Y = 0.072187f; + #endif // hifi_common_h diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 105baddb92..2834ff81e0 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -12,14 +12,18 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); + + ui.checkBoxInteractiveMode->setChecked(true); + + ui.progressBar->setVisible(false); } void AutoTester::on_evaluateTestsButton_clicked() { - test.evaluateTests(); + test.evaluateTests(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); } void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { - test.evaluateTestsRecursively(); + test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); } void AutoTester::on_createRecursiveScriptButton_clicked() { @@ -30,6 +34,10 @@ void AutoTester::on_createTestButton_clicked() { test.createTest(); } +void AutoTester::on_deleteOldSnapshotsButton_clicked() { + test.deleteOldSnapshots(); +} + void AutoTester::on_closeButton_clicked() { exit(0); } \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index acfea32ba1..110b26a7ba 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -22,10 +22,11 @@ public: AutoTester(QWidget *parent = Q_NULLPTR); private slots: -void on_evaluateTestsButton_clicked(); -void on_evaluateTestsRecursivelyButton_clicked(); -void on_createRecursiveScriptButton_clicked(); + void on_evaluateTestsButton_clicked(); + void on_evaluateTestsRecursivelyButton_clicked(); + void on_createRecursiveScriptButton_clicked(); void on_createTestButton_clicked(); + void on_deleteOldSnapshotsButton_clicked(); void on_closeButton_clicked(); private: diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 7032ef9710..4b2f9520a9 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 286 - 470 + 607 + 395 @@ -17,9 +17,9 @@ - 60 - 360 - 160 + 190 + 300 + 220 40 @@ -30,9 +30,9 @@ - 60 - 270 - 160 + 360 + 130 + 220 40 @@ -43,9 +43,9 @@ - 60 - 20 - 160 + 20 + 75 + 220 40 @@ -56,9 +56,9 @@ - 60 - 210 - 160 + 360 + 75 + 220 40 @@ -69,9 +69,9 @@ - 60 - 75 - 160 + 20 + 130 + 220 40 @@ -79,13 +79,55 @@ Evaluate Tests Recursively + + + + 23 + 40 + 131 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Interactive Mode + + + + + + 20 + 190 + 255 + 23 + + + + 24 + + + + + + 360 + 240 + 220 + 40 + + + + Delete Old Snapshots + + 0 0 - 286 + 607 21 diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index 07664a1667..fe22412522 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -11,11 +11,48 @@ #include +#include + MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { setupUi(this); expectedImage->setScaledContents(true); resultImage->setScaledContents(true); + diffImage->setScaledContents(true); +} + +QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) { + // This is an optimization, as QImage.setPixel() is embarrassingly slow + unsigned char* buffer = new unsigned char[expectedImage.height() * expectedImage.width() * 3]; + + // loop over each pixel + for (int y = 0; y < expectedImage.height(); ++y) { + for (int x = 0; x < expectedImage.width(); ++x) { + QRgb pixelP = expectedImage.pixel(QPoint(x, y)); + QRgb pixelQ = resultImage.pixel(QPoint(x, y)); + + // Convert to luminance + double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); + double q = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); + + // The intensity value is modified to increase the brightness of the displayed image + double absoluteDifference = fabs(p - q) / 255.0; + double modifiedDifference = sqrt(absoluteDifference); + + int difference = (int)(modifiedDifference * 255.0); + + buffer[3 * (x + y * expectedImage.width()) + 0] = difference; + buffer[3 * (x + y * expectedImage.width()) + 1] = difference; + buffer[3 * (x + y * expectedImage.width()) + 2] = difference; + } + } + + QImage diffImage(buffer, expectedImage.width(), expectedImage.height(), QImage::Format_RGB888); + QPixmap resultPixmap = QPixmap::fromImage(diffImage); + + delete[] buffer; + + return resultPixmap; } void MismatchWindow::setTestFailure(TestFailure testFailure) { @@ -24,10 +61,19 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) { imagePath->setText("Path to test: " + testFailure._pathname); expectedFilename->setText(testFailure._expectedImageFilename); - expectedImage->setPixmap(QPixmap(testFailure._pathname + testFailure._expectedImageFilename)); - resultFilename->setText(testFailure._actualImageFilename); - resultImage->setPixmap(QPixmap(testFailure._pathname + testFailure._actualImageFilename)); + + QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename); + QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename); + + diffPixmap = computeDiffPixmap( + QImage(testFailure._pathname + testFailure._expectedImageFilename), + QImage(testFailure._pathname + testFailure._actualImageFilename) + ); + + expectedImage->setPixmap(expectedPixmap); + resultImage->setPixmap(actualPixmap); + diffImage->setPixmap(diffPixmap); } void MismatchWindow::on_passTestButton_clicked() { @@ -44,3 +90,7 @@ void MismatchWindow::on_abortTestsButton_clicked() { _userResponse = USER_RESPONSE_ABORT; close(); } + +QPixmap MismatchWindow::getComparisonImage() { + return diffPixmap; +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h index 7c72b7b0b7..ad8be16580 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.h +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -25,6 +25,9 @@ public: UserResponse getUserResponse() { return _userResponse; } + QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); + QPixmap getComparisonImage(); + private slots: void on_passTestButton_clicked(); void on_failTestButton_clicked(); @@ -32,6 +35,8 @@ private slots: private: UserResponse _userResponse{ USER_RESPONSE_INVALID }; + + QPixmap diffPixmap; }; diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui index cab6c61e1c..5ecf966df5 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.ui +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -6,8 +6,8 @@ 0 0 - 1585 - 694 + 1782 + 942 @@ -16,10 +16,10 @@ - 20 - 170 - 720 - 362 + 10 + 25 + 800 + 450 @@ -29,28 +29,41 @@ - 760 - 170 - 720 - 362 + 900 + 25 + 800 + 450 result image + + + + 540 + 480 + 800 + 450 + + + + diff image + + - 760 - 90 - 800 + 60 + 660 + 480 28 - 16 + 12 @@ -60,15 +73,15 @@ - 40 - 90 - 700 + 60 + 630 + 480 28 - 16 + 12 @@ -78,15 +91,15 @@ - 40 - 30 + 20 + 600 1200 28 - 16 + 12 @@ -97,7 +110,7 @@ 30 - 600 + 790 75 23 @@ -109,8 +122,8 @@ - 330 - 600 + 120 + 790 75 23 @@ -122,34 +135,60 @@ - 630 - 600 - 75 + 210 + 790 + 121 23 - Abort Tests + Abort current test - 810 - 600 - 720 + 30 + 850 + 500 28 - 16 + 12 similarity + + + + 30 + 5 + 151 + 16 + + + + Expected Image + + + + + + 930 + 5 + 151 + 16 + + + + Actual Image + + From 5b7ef9a3d366e269548e6f0aa75559ffe79b26b3 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 4 Jan 2018 09:13:58 -0800 Subject: [PATCH 49/90] Minor cleanup. --- .../entities-renderer/src/RenderableZoneEntityItem.h | 2 +- libraries/entities/src/EntityItemProperties.h | 8 +++++--- libraries/entities/src/ZoneEntityItem.h | 7 +++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 2664180433..150b06d912 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -91,10 +91,10 @@ private: BackgroundMode _backgroundMode{ BACKGROUND_MODE_INHERIT }; - ComponentMode _hazeMode { COMPONENT_MODE_INHERIT }; ComponentMode _keyLightMode { COMPONENT_MODE_INHERIT }; ComponentMode _ambientLightMode { COMPONENT_MODE_INHERIT }; ComponentMode _skyboxMode { COMPONENT_MODE_INHERIT }; + ComponentMode _hazeMode { COMPONENT_MODE_INHERIT }; indexed_container::Index _sunIndex{ LightStage::INVALID_INDEX }; indexed_container::Index _shadowIndex{ LightStage::INVALID_INDEX }; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index e7c4c3909b..6ad89275c4 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -181,10 +181,12 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_BACKGROUND_MODE, BackgroundMode, backgroundMode, BackgroundMode, BACKGROUND_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup); + DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); + DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); + DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); + + // This is the default mode for zone creation DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index bdc5821da6..08bb93c573 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -132,10 +132,13 @@ protected: QString _compoundShapeURL; BackgroundMode _backgroundMode { BACKGROUND_MODE_INHERIT }; - uint32_t _hazeMode { COMPONENT_MODE_INHERIT }; + + // The following 3 values are the defaults for zone creation uint32_t _keyLightMode { COMPONENT_MODE_INHERIT }; - uint32_t _ambientLightMode { COMPONENT_MODE_INHERIT }; uint32_t _skyboxMode { COMPONENT_MODE_INHERIT }; + uint32_t _ambientLightMode { COMPONENT_MODE_INHERIT }; + + uint32_t _hazeMode { COMPONENT_MODE_INHERIT }; SkyboxPropertyGroup _skyboxProperties; HazePropertyGroup _hazeProperties; From e8770ec204231ff3393ecc2cfb9dda448d7046cf Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 4 Jan 2018 11:30:02 -0800 Subject: [PATCH 50/90] TEMPORARY fix for older content not containing these fields (keylight, skybox, ambient light). --- libraries/entities/src/EntityTree.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8f780355db..580c4b4482 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2281,6 +2281,17 @@ bool EntityTree::readFromMap(QVariantMap& map) { properties.setOwningAvatarID(myNodeID); } + // TEMPORARY fix for older content not containing these fields + if (!entityMap.contains("keyLightMode")) { + properties.setKeyLightMode(COMPONENT_MODE_ENABLED); + } + if (!entityMap.contains("skyboxMode")) { + properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + } + if (!entityMap.contains("ambientLightMode")) { + properties.setAmbientLightMode(COMPONENT_MODE_ENABLED); + } + EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); From 10d494b51de4243b1258625933c4ca6a507c26da Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 4 Jan 2018 12:32:30 -0800 Subject: [PATCH 51/90] 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 8bd2985f1f97f3231e842775efbc05fff5a1811a Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 4 Jan 2018 13:06:28 -0800 Subject: [PATCH 52/90] Simplified code. --- .../src/RenderableZoneEntityItem.cpp | 50 ++++--------------- .../src/RenderableZoneEntityItem.h | 5 -- .../entities/src/EntityItemProperties.cpp | 29 +---------- libraries/entities/src/EntityItemProperties.h | 5 +- libraries/entities/src/ZoneEntityItem.cpp | 8 +-- 5 files changed, 16 insertions(+), 81 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 0d975759b4..138ecb9f38 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -163,48 +163,23 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { if (_visible) { // Finally, push the light visible in the frame - if (_keyLightMode == COMPONENT_MODE_DISABLED && _sunOnIndex == NO_STORED_VALUE) { - // Just turned off, store previous value before changing - _sunOnIndex = _sunIndex; - _sunIndex = _stage->getSunOffLight(); - } else if (_keyLightMode == COMPONENT_MODE_ENABLED && _sunOnIndex != NO_STORED_VALUE) { - // Just turned on, restore previous value before clearing stored value - _sunIndex = _sunOnIndex; - _sunOnIndex = NO_STORED_VALUE; - } - - if (_keyLightMode != COMPONENT_MODE_INHERIT) { + if (_keyLightMode == COMPONENT_MODE_DISABLED) { + _stage->_currentFrame.pushSunLight(_stage->getSunOffLight()); + } else if (_keyLightMode == COMPONENT_MODE_ENABLED) { _stage->_currentFrame.pushSunLight(_sunIndex); } // The background only if the mode is not inherit - if (_skyboxMode == COMPONENT_MODE_DISABLED && _skyboxOnIndex == NO_STORED_VALUE) { - // Just turned off, store previous value before changing - _skyboxOnIndex = _backgroundIndex; - _backgroundIndex = INVALID_INDEX; - } else if (_skyboxMode == COMPONENT_MODE_ENABLED && _skyboxOnIndex != NO_STORED_VALUE) { - // Just turned on, restore previous value before clearing stored value - _backgroundIndex = _skyboxOnIndex; - _skyboxOnIndex = NO_STORED_VALUE; - } - - // _backgroundMode is kept for legacy purposes - if (_skyboxMode != COMPONENT_MODE_INHERIT || _backgroundMode != BACKGROUND_MODE_INHERIT) { + if (_skyboxMode == COMPONENT_MODE_DISABLED) { + _backgroundStage->_currentFrame.pushBackground(INVALID_INDEX); + } else if (_skyboxMode == COMPONENT_MODE_ENABLED) { _backgroundStage->_currentFrame.pushBackground(_backgroundIndex); } // The ambient light only if it has a valid texture to render with - if (_ambientLightMode == COMPONENT_MODE_DISABLED && _ambientOnIndex == NO_STORED_VALUE) { - // Just turned off, store previous value before changing - _ambientOnIndex = _ambientIndex; - _ambientIndex = _stage->getAmbientOffLight(); - } else if (_ambientLightMode == COMPONENT_MODE_ENABLED && _ambientOnIndex != NO_STORED_VALUE) { - // Just turned on, restore previous value before clearing stored value - _ambientIndex = _ambientOnIndex; - _ambientOnIndex = NO_STORED_VALUE; - } - - if (_ambientLightMode != COMPONENT_MODE_INHERIT && _validAmbientTexture) { + if (_ambientLightMode == COMPONENT_MODE_DISABLED) { + _stage->_currentFrame.pushAmbientLight(_stage->getAmbientOffLight()); + } else if (_ambientLightMode == COMPONENT_MODE_ENABLED) { _stage->_currentFrame.pushAmbientLight(_ambientIndex); } @@ -222,11 +197,6 @@ void ZoneEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& } #endif Parent::removeFromScene(scene, transaction); - - // clear flags - _sunOnIndex = NO_STORED_VALUE; - _ambientOnIndex = NO_STORED_VALUE; - _skyboxOnIndex = NO_STORED_VALUE; } void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { @@ -276,7 +246,7 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateKeySunFromEntity(entity); } - if (ambientLightChanged || skyboxChanged) { + if (ambientLightChanged) { updateAmbientLightFromEntity(entity); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 150b06d912..37b40e01b6 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -100,11 +100,6 @@ private: indexed_container::Index _shadowIndex{ LightStage::INVALID_INDEX }; indexed_container::Index _ambientIndex{ LightStage::INVALID_INDEX }; - const int NO_STORED_VALUE { -1 }; - indexed_container::Index _sunOnIndex { NO_STORED_VALUE }; - indexed_container::Index _ambientOnIndex { NO_STORED_VALUE }; - indexed_container::Index _skyboxOnIndex { NO_STORED_VALUE }; - BackgroundStagePointer _backgroundStage; BackgroundStage::Index _backgroundIndex{ BackgroundStage::INVALID_INDEX }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index d5a58c9580..0a53f96a59 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -224,7 +224,7 @@ QString EntityItemProperties::getHazeModeAsString() const { } } -QString EntityItemProperties::getHazeModeString(uint32_t mode) { +QString EntityItemProperties::getComponentModeString(uint32_t mode) { // return "inherit" if mode is not valid if (mode < COMPONENT_MODE_ITEM_COUNT) { return COMPONENT_MODES[mode].second; @@ -253,15 +253,6 @@ QString EntityItemProperties::getKeyLightModeAsString() const { } } -QString EntityItemProperties::getKeyLightModeString(uint32_t mode) { - // return "enabled" if mode is not valid - if (mode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[mode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } -} - void EntityItemProperties::setKeyLightModeFromString(const QString& keyLightMode) { auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { return (pair.second == keyLightMode); @@ -282,15 +273,6 @@ QString EntityItemProperties::getAmbientLightModeAsString() const { } } -QString EntityItemProperties::getAmbientLightModeString(uint32_t mode) { - // return "enabled" if mode is not valid - if (mode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[mode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } -} - void EntityItemProperties::setAmbientLightModeFromString(const QString& ambientLightMode) { auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { return (pair.second == ambientLightMode); @@ -311,15 +293,6 @@ QString EntityItemProperties::getSkyboxModeAsString() const { } } -QString EntityItemProperties::getSkyboxModeString(uint32_t mode) { - // return "enabled" if mode is not valid - if (mode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[mode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } -} - void EntityItemProperties::setSkyboxModeFromString(const QString& skyboxMode) { auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { return (pair.second == skyboxMode); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6ad89275c4..587b9189a1 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -255,10 +255,7 @@ public: DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS); static QString getBackgroundModeString(BackgroundMode mode); - static QString getHazeModeString(uint32_t mode); - static QString getKeyLightModeString(uint32_t mode); - static QString getAmbientLightModeString(uint32_t mode); - static QString getSkyboxModeString(uint32_t mode); + static QString getComponentModeString(uint32_t mode); public: float getMaxDimension() const { return glm::compMax(_dimensions); } diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 32db157b5e..87b417588d 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -290,10 +290,10 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << " _backgroundMode:" << EntityItemProperties::getBackgroundModeString(_backgroundMode); - qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getHazeModeString(_hazeMode); - qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getKeyLightModeString(_keyLightMode); - qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getAmbientLightModeString(_ambientLightMode); - qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getSkyboxModeString(_skyboxMode); + qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getComponentModeString(_hazeMode); + qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getComponentModeString(_keyLightMode); + qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeString(_ambientLightMode); + qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeString(_skyboxMode); _keyLightProperties.debugDump(); _ambientLightProperties.debugDump(); From 14447c26a0c69be9505391ea5903778777322bfa Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 4 Jan 2018 13:47:31 -0800 Subject: [PATCH 53/90] 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 54/90] 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 55/90] 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 1e2413bade0550036ab51a5d24c74e93c824df7e Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 4 Jan 2018 16:01:22 -0800 Subject: [PATCH 56/90] Removed unneeded file. --- interface/resources/qml/js/Utils.jsc | Bin 6516 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 interface/resources/qml/js/Utils.jsc diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc deleted file mode 100644 index dec1adb6f865f057bee2a149c2f75b38999f104d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6516 zcmcIoUu;`v75^pIE$JA$?grI>!GgGms+6Xks;Qcgh$~04#kCaHG_+`>$r6WVijypL zsuXP~Ra&U=q8U_82JzsB3R!3ZKlH&0L-Mr1`T(q*BGyS2MAJY{6(P^?5LFXKzweLl zy|%BDXywbp%{|}w&fniT=evI4{L#^|k>toD06O>V!pnF6m>li^4(@P%GC1u72ktf z$kV>Bb^>=gfsZN*FfhNA zIIsBS`vj)cJLCFoBt?wL!&qM+?g5;4X?p@=;so_QhYLs|j&mw6-@5zOH^^x!NeFpdfI2zm5k7ULMfDB|eBtniA~%nHgm^xy>~Fo_;KFWydy zj7{Pk4hae&{}seBC2YJP{5db|oxa*zNE1HjF2>j z34s*uO=3pyF>lWcPG_Brr2Y|E+~~Jc@l89GsMx8wa`g9d|D#$o^_N(Byem@F2SUpP zSu_=}qKhkCp~br$=H-9YYPDEuyeqP}(H&xsLu6U=+o=yjwO5aI0FlL&?!NS3uy`V-a-GvR+09{mcmwX znOY?G73y<4Dm~wD%cjRZvL9%cO^1DC-x10@vK{UwyMH6{=CRwcq{;4uX4#IHO|qY9 zmhD8aNw!7VbwouMH~NVlJ3Uvn(}{|mj&DX5SNgyAro*%TZmm|6klHa`*)&_@HJ08N zOFz?94Vl5?!D`6d8@7sW+M|+IaMQXaEgwto3%zyxuz=&@M)xvXnoLu^5dx2>q~^9_ zysM&^4ZRh%R?uyL|CE@eXgX|ViMkuKHf$B888_uyLB8GAh%7C$qX=^bdp&Ucu})NL z6**rO`6d+{jIC5KyP`g5b1}NOF|&j0V(0ngwdntrUpgt$#!=>z-%O`;SmZZUdqIXkOZN)_$2$R--)0C}*OCl@ zg8eo^@DmMtuU1{W?#lAUT&Z^zFRyDNy$!|7&k|Q>#b8P-9M}5-nmUX z6|dN-MA=Twt+gHc`-JLNL*LyTRTIrg+Wu&|Gm`EdrpZ|k=`2YZ4{3&^D<0B3Negb$ z0v(Q~E?M)uaft3Ml`L?^aAT%%gP(5PINP|9RU5;N8!T9!I3+jO<@qTg@t{)2oCU+W zTuPpDQ5|~0RanwsS8Yi{ReABWG*A-Cl0d^Gp&|)1ND?+#2+{fkb()CkIsJ`yme&IG zDQsn(kvbkRrz(Bv0qa`m;yun9^YVkz%>x5gZk?Y=p*QY5EN*LuR2Pgc*;X!8d$k~L zZ?5Z{9RTbjWy-v{&JQ{E{+y<0rI}tb+^CvEw<5(~{G98o@)@ut!|*9?n#>oBl@(8T!oq z@@%bh8=m#PqGNsfmd6Elr`Pn=BF{@2Q*6x!N4Drbj&iG zemOa!OO3_&xE4olno_Zklq0FWlD*Vd;=!yu$X|{;Q}&-qW7ZpQQKxR zrJ)AbZ}R9b>lLq$vaKC>-+T1S>S$J#(OT6!wd%pry6y`MU)JR^zX3g#A5@m-L>i(O zH|U$1Tx)hZQMS|ZO4SU`HQ)T-_Kq(-zlO#9s+xOWs@SP`*-jb}jb@IBRX^MbL>=RrX+YW(th#e0XSW*DdQ~3| z9(KR`D}Nmlxc5Ry509jk^&cMDR>R}BPOGleTeU`=7*SX2fYn+Ctc<^wOe-x>M~gEM zGI~T*&1b9Ls&X{#bQu1m-C)M3;H9Y51OR-GY+KOQ=IwM3b2eVg_}$6m$W zaQXH_$NQ?e6&cOzO?i9KD){HE&?;{~?Wm8t%{n>?DsP+A@_Lh&0jub*rPxZ#*BmVl zI*KlI+`C`sc!NdjdsV8Mr{8t=SpFWNJB~N)`okhGnt~o9d{I9NZ2!B0ZJY<|U+nX$ cHQ%<=i8VVNFIUarOZEOd?D=Kp&$U|ZfBbNDS^xk5 From adbc4d0c395bdb13cb7efa0794e164803355a039 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 4 Jan 2018 18:23:25 -0800 Subject: [PATCH 57/90] Removed background mode from code (still in the protocol). --- .../src/RenderableZoneEntityItem.cpp | 1 - libraries/entities/src/ZoneEntityItem.cpp | 4 - libraries/entities/src/ZoneEntityItem.h | 3 - .../src/SceneScriptingInterface.cpp | 153 ++++++++++++++++ .../src/SceneScriptingInterface.h | 166 ++++++++++++++++++ 5 files changed, 319 insertions(+), 8 deletions(-) create mode 100644 libraries/script-engine/src/SceneScriptingInterface.cpp create mode 100644 libraries/script-engine/src/SceneScriptingInterface.h diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 138ecb9f38..8cc4a0194f 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -385,7 +385,6 @@ void ZoneEntityRenderer::updateKeyBackgroundFromEntity(const TypedEntityPointer& setSkyboxMode((ComponentMode)entity->getSkyboxMode()); editBackground(); - setBackgroundMode(entity->getBackgroundMode()); setSkyboxColor(_skyboxProperties.getColorVec3()); setProceduralUserData(entity->getUserData()); setSkyboxURL(_skyboxProperties.getURL()); diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 87b417588d..f2fb480353 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -62,7 +62,6 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundMode, getBackgroundMode); // Contains a QString property, must be synchronized withReadLock([&] { @@ -116,7 +115,6 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode); // Contains a QString property, must be synchronized withWriteLock([&] { @@ -175,7 +173,6 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); - READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); int bytesFromSkybox; withWriteLock([&] { @@ -265,7 +262,6 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); - APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)getBackgroundMode()); // could this be a uint16?? _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 08bb93c573..aca7a0fd1b 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -69,9 +69,6 @@ public: KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock([&] { return _keyLightProperties; }); } AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock([&] { return _ambientLightProperties; }); } - void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; _backgroundPropertiesChanged = true; } - BackgroundMode getBackgroundMode() const { return _backgroundMode; } - void setHazeMode(const uint32_t value); uint32_t getHazeMode() const; diff --git a/libraries/script-engine/src/SceneScriptingInterface.cpp b/libraries/script-engine/src/SceneScriptingInterface.cpp new file mode 100644 index 0000000000..3883b948df --- /dev/null +++ b/libraries/script-engine/src/SceneScriptingInterface.cpp @@ -0,0 +1,153 @@ +// +// SceneScriptingInterface.cpp +// libraries/script-engine +// +// Created by Sam Gateau on 2/24/15. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SceneScriptingInterface.h" + +#include + +float SceneScripting::Location::getLongitude() const { + return _skyStage->getOriginLongitude(); +} + +float SceneScripting::Location::getLatitude() const { + return _skyStage->getOriginLatitude(); +} + +float SceneScripting::Location::getAltitude() const { + return _skyStage->getOriginSurfaceAltitude(); +} + +void SceneScripting::Location::setLongitude(float longitude) { + _skyStage->setOriginLongitude(longitude); +} + +void SceneScripting::Location::setLatitude(float latitude) { + _skyStage->setOriginLatitude(latitude); +} + +void SceneScripting::Location::setAltitude(float altitude) { + _skyStage->setOriginSurfaceAltitude(altitude); +} + +void SceneScripting::Time::setHour(float hour) { + _skyStage->setDayTime(hour); +} + +float SceneScripting::Time::getHour() const { + return _skyStage->getDayTime(); +} + +void SceneScripting::Time::setDay(int day) { + _skyStage->setYearTime(day); +} + +int SceneScripting::Time::getDay() const { + return _skyStage->getYearTime(); +} + +glm::vec3 SceneScripting::KeyLight::getColor() const { + return _skyStage->getSunColor(); +} + +void SceneScripting::KeyLight::setColor(const glm::vec3& color) { + _skyStage->setSunColor(color); +} + +float SceneScripting::KeyLight::getIntensity() const { + return _skyStage->getSunIntensity(); +} + +void SceneScripting::KeyLight::setIntensity(float intensity) { + _skyStage->setSunIntensity(intensity); +} + +float SceneScripting::KeyLight::getAmbientIntensity() const { + return _skyStage->getSunAmbientIntensity(); +} + +void SceneScripting::KeyLight::setAmbientIntensity(float intensity) { + _skyStage->setSunAmbientIntensity(intensity); +} + +void SceneScripting::KeyLight::setAmbientSphere(const gpu::SHPointer& sphere) { + _skyStage->setSunAmbientSphere(sphere); +} + +void SceneScripting::KeyLight::setAmbientMap(const gpu::TexturePointer& map) { + _skyStage->setSunAmbientMap(map); +} + + +glm::vec3 SceneScripting::KeyLight::getDirection() const { + return _skyStage->getSunDirection(); +} + +void SceneScripting::KeyLight::setDirection(const glm::vec3& direction) { + _skyStage->setSunDirection(direction); +} + +void SceneScripting::Stage::setOrientation(const glm::quat& orientation) const { + _skyStage->setOriginOrientation(orientation); +} + +void SceneScripting::Stage::setLocation(float longitude, float latitude, float altitude) { + _skyStage->setOriginLocation(longitude, latitude, altitude); +} + +void SceneScripting::Stage::setSunModelEnable(bool isEnabled) { + _skyStage->setSunModelEnable(isEnabled); +} + +bool SceneScripting::Stage::isSunModelEnabled() const { + return _skyStage->isSunModelEnabled(); +} + +void SceneScripting::Stage::setBackgroundMode(const QString& mode) { + if (mode == QString("inherit")) { + _skyStage->setBackgroundMode(model::SunSkyStage::NO_BACKGROUND); + } else if (mode == QString("skybox")) { + _skyStage->setBackgroundMode(model::SunSkyStage::SKY_BOX); + } +} + +QString SceneScripting::Stage::getBackgroundMode() const { + switch (_skyStage->getBackgroundMode()) { + case model::SunSkyStage::NO_BACKGROUND: + return QString("inherit"); + case model::SunSkyStage::SKY_BOX: + return QString("skybox"); + default: + return QString("inherit"); + }; +} + +SceneScriptingInterface::SceneScriptingInterface() : _stage{ new SceneScripting::Stage{ _skyStage } } { + // Let's make sure the sunSkyStage is using a proceduralSkybox + _skyStage->setSkybox(model::SkyboxPointer(new ProceduralSkybox())); +} + +void SceneScriptingInterface::setShouldRenderAvatars(bool shouldRenderAvatars) { + if (shouldRenderAvatars != _shouldRenderAvatars) { + _shouldRenderAvatars = shouldRenderAvatars; + emit shouldRenderAvatarsChanged(_shouldRenderAvatars); + } +} + +void SceneScriptingInterface::setShouldRenderEntities(bool shouldRenderEntities) { + if (shouldRenderEntities != _shouldRenderEntities) { + _shouldRenderEntities = shouldRenderEntities; + emit shouldRenderEntitiesChanged(_shouldRenderEntities); + } +} + +model::SunSkyStagePointer SceneScriptingInterface::getSkyStage() const { + return _skyStage; +} diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h new file mode 100644 index 0000000000..7bc22eb3e6 --- /dev/null +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -0,0 +1,166 @@ +// +// SceneScriptingInterface.h +// libraries/script-engine +// +// Created by Sam Gateau on 2/24/15. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SceneScriptingInterface_h +#define hifi_SceneScriptingInterface_h + +#include // QObject +#include // Dependency + +#include "model/Stage.h" + +// TODO: if QT moc ever supports nested classes, subclass these to the interface instead of namespacing +namespace SceneScripting { + class Location : public QObject { + Q_OBJECT + + public: + Location(model::SunSkyStagePointer skyStage) : _skyStage{ skyStage } {} + + Q_PROPERTY(float longitude READ getLongitude WRITE setLongitude) + Q_PROPERTY(float latitude READ getLatitude WRITE setLatitude) + Q_PROPERTY(float altitude READ getAltitude WRITE setAltitude) + + float getLongitude() const; + float getLatitude() const; + float getAltitude() const; + void setLongitude(float longitude); + void setLatitude(float latitude); + void setAltitude(float altitude); + + protected: + model::SunSkyStagePointer _skyStage; + }; + using LocationPointer = std::unique_ptr; + + class Time : public QObject { + Q_OBJECT + + public: + Time(model::SunSkyStagePointer skyStage) : _skyStage{ skyStage } {} + + Q_PROPERTY(float hour READ getHour WRITE setHour) + Q_PROPERTY(int day READ getDay WRITE setDay) + + float getHour() const; + void setHour(float hour); + int getDay() const; + void setDay(int day); + + protected: + model::SunSkyStagePointer _skyStage; + }; + using TimePointer = std::unique_ptr @@ -17,9 +17,9 @@ - 190 - 300 - 220 + 60 + 360 + 160 40 @@ -30,9 +30,9 @@ - 360 - 130 - 220 + 60 + 270 + 160 40 @@ -43,9 +43,9 @@ - 20 - 75 - 220 + 60 + 20 + 160 40 @@ -56,9 +56,9 @@ - 360 - 75 - 220 + 60 + 210 + 160 40 @@ -69,9 +69,9 @@ - 20 - 130 - 220 + 60 + 75 + 160 40 @@ -79,55 +79,13 @@ Evaluate Tests Recursively - - - - 23 - 40 - 131 - 20 - - - - <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> - - - Interactive Mode - - - - - - 20 - 190 - 255 - 23 - - - - 24 - - - - - - 360 - 240 - 220 - 40 - - - - Delete Old Snapshots - - 0 0 - 607 + 286 21 diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index fe22412522..07664a1667 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -11,48 +11,11 @@ #include -#include - MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { setupUi(this); expectedImage->setScaledContents(true); resultImage->setScaledContents(true); - diffImage->setScaledContents(true); -} - -QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) { - // This is an optimization, as QImage.setPixel() is embarrassingly slow - unsigned char* buffer = new unsigned char[expectedImage.height() * expectedImage.width() * 3]; - - // loop over each pixel - for (int y = 0; y < expectedImage.height(); ++y) { - for (int x = 0; x < expectedImage.width(); ++x) { - QRgb pixelP = expectedImage.pixel(QPoint(x, y)); - QRgb pixelQ = resultImage.pixel(QPoint(x, y)); - - // Convert to luminance - double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); - double q = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); - - // The intensity value is modified to increase the brightness of the displayed image - double absoluteDifference = fabs(p - q) / 255.0; - double modifiedDifference = sqrt(absoluteDifference); - - int difference = (int)(modifiedDifference * 255.0); - - buffer[3 * (x + y * expectedImage.width()) + 0] = difference; - buffer[3 * (x + y * expectedImage.width()) + 1] = difference; - buffer[3 * (x + y * expectedImage.width()) + 2] = difference; - } - } - - QImage diffImage(buffer, expectedImage.width(), expectedImage.height(), QImage::Format_RGB888); - QPixmap resultPixmap = QPixmap::fromImage(diffImage); - - delete[] buffer; - - return resultPixmap; } void MismatchWindow::setTestFailure(TestFailure testFailure) { @@ -61,19 +24,10 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) { imagePath->setText("Path to test: " + testFailure._pathname); expectedFilename->setText(testFailure._expectedImageFilename); + expectedImage->setPixmap(QPixmap(testFailure._pathname + testFailure._expectedImageFilename)); + resultFilename->setText(testFailure._actualImageFilename); - - QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename); - QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename); - - diffPixmap = computeDiffPixmap( - QImage(testFailure._pathname + testFailure._expectedImageFilename), - QImage(testFailure._pathname + testFailure._actualImageFilename) - ); - - expectedImage->setPixmap(expectedPixmap); - resultImage->setPixmap(actualPixmap); - diffImage->setPixmap(diffPixmap); + resultImage->setPixmap(QPixmap(testFailure._pathname + testFailure._actualImageFilename)); } void MismatchWindow::on_passTestButton_clicked() { @@ -90,7 +44,3 @@ void MismatchWindow::on_abortTestsButton_clicked() { _userResponse = USER_RESPONSE_ABORT; close(); } - -QPixmap MismatchWindow::getComparisonImage() { - return diffPixmap; -} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h index ad8be16580..7c72b7b0b7 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.h +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -25,9 +25,6 @@ public: UserResponse getUserResponse() { return _userResponse; } - QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); - QPixmap getComparisonImage(); - private slots: void on_passTestButton_clicked(); void on_failTestButton_clicked(); @@ -35,8 +32,6 @@ private slots: private: UserResponse _userResponse{ USER_RESPONSE_INVALID }; - - QPixmap diffPixmap; }; diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui index 5ecf966df5..cab6c61e1c 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.ui +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -6,8 +6,8 @@ 0 0 - 1782 - 942 + 1585 + 694 @@ -16,10 +16,10 @@ - 10 - 25 - 800 - 450 + 20 + 170 + 720 + 362 @@ -29,41 +29,28 @@ - 900 - 25 - 800 - 450 + 760 + 170 + 720 + 362 result image - - - - 540 - 480 - 800 - 450 - - - - diff image - - - 60 - 660 - 480 + 760 + 90 + 800 28 - 12 + 16 @@ -73,15 +60,15 @@ - 60 - 630 - 480 + 40 + 90 + 700 28 - 12 + 16 @@ -91,15 +78,15 @@ - 20 - 600 + 40 + 30 1200 28 - 12 + 16 @@ -110,7 +97,7 @@ 30 - 790 + 600 75 23 @@ -122,8 +109,8 @@ - 120 - 790 + 330 + 600 75 23 @@ -135,60 +122,34 @@ - 210 - 790 - 121 + 630 + 600 + 75 23 - Abort current test + Abort Tests - 30 - 850 - 500 + 810 + 600 + 720 28 - 12 + 16 similarity - - - - 30 - 5 - 151 - 16 - - - - Expected Image - - - - - - 930 - 5 - 151 - 16 - - - - Actual Image - - From 3963c0a5a183d8cb0e1f8e4880b60c2b19dd749a Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 5 Jan 2018 12:22:20 -0800 Subject: [PATCH 61/90] Code review improvements. --- .../entities/src/EntityItemProperties.cpp | 63 +++++++------------ libraries/entities/src/EntityItemProperties.h | 11 ++++ libraries/entities/src/EntityTree.cpp | 30 ++++----- 3 files changed, 49 insertions(+), 55 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 0a53f96a59..76a591f20a 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -208,20 +208,18 @@ void EntityItemProperties::setBackgroundModeFromString(const QString& background } } -using ComponentPair = std::pair; -const std::array COMPONENT_MODES = { { - ComponentPair{ COMPONENT_MODE_INHERIT,{ "inherit" } }, - ComponentPair{ COMPONENT_MODE_DISABLED,{ "disabled" } }, - ComponentPair{ COMPONENT_MODE_ENABLED,{ "enabled" } } -} }; - -QString EntityItemProperties::getHazeModeAsString() const { - // return "inherit" if _hazeMode is not valid - if (_hazeMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_hazeMode].second; +QString EntityItemProperties::getComponentModeAsString(uint32_t mode) { + // return "inherit" if mode is not valid + if (mode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[mode].second; } else { return COMPONENT_MODES[COMPONENT_MODE_INHERIT].second; } + +} + +QString EntityItemProperties::getHazeModeAsString() const { + return getComponentModeAsString(_hazeMode); } QString EntityItemProperties::getComponentModeString(uint32_t mode) { @@ -233,10 +231,14 @@ QString EntityItemProperties::getComponentModeString(uint32_t mode) { } } -void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == hazeMode); +const auto EntityItemProperties::findComponent(const QString& mode) { + return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { + return (pair.second == mode); }); +} + +void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { + auto result = findComponent(hazeMode); if (result != COMPONENT_MODES.end()) { _hazeMode = result->first; @@ -245,18 +247,11 @@ void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { } QString EntityItemProperties::getKeyLightModeAsString() const { - // return "enabled" if _keyLightMode is not valid - if (_keyLightMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_keyLightMode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } + return getComponentModeAsString(_keyLightMode); } void EntityItemProperties::setKeyLightModeFromString(const QString& keyLightMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == keyLightMode); - }); + auto result = findComponent(keyLightMode); if (result != COMPONENT_MODES.end()) { _keyLightMode = result->first; @@ -265,18 +260,11 @@ void EntityItemProperties::setKeyLightModeFromString(const QString& keyLightMode } QString EntityItemProperties::getAmbientLightModeAsString() const { - // return "enabled" if _ambientLightMode is not valid - if (_ambientLightMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_ambientLightMode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } + return getComponentModeAsString(_ambientLightMode); } void EntityItemProperties::setAmbientLightModeFromString(const QString& ambientLightMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == ambientLightMode); - }); + auto result = findComponent(ambientLightMode); if (result != COMPONENT_MODES.end()) { _ambientLightMode = result->first; @@ -285,18 +273,11 @@ void EntityItemProperties::setAmbientLightModeFromString(const QString& ambientL } QString EntityItemProperties::getSkyboxModeAsString() const { - // return "enabled" if _skyboxMode is not valid - if (_skyboxMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_skyboxMode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } + return getComponentModeAsString(_skyboxMode); } void EntityItemProperties::setSkyboxModeFromString(const QString& skyboxMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == skyboxMode); - }); + auto result = findComponent(skyboxMode); if (result != COMPONENT_MODES.end()) { _skyboxMode = result->first; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 587b9189a1..467a89166c 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -47,6 +47,13 @@ const quint64 UNKNOWN_CREATED_TIME = 0; +using ComponentPair = std::pair; +const std::array COMPONENT_MODES = { { + ComponentPair { COMPONENT_MODE_INHERIT, { "inherit" } }, + ComponentPair { COMPONENT_MODE_DISABLED, { "disabled" } }, + ComponentPair { COMPONENT_MODE_ENABLED, { "enabled" } } +} }; + /// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an /// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete /// set of entity item properties via JavaScript hashes/QScriptValues @@ -255,7 +262,11 @@ public: DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS); static QString getBackgroundModeString(BackgroundMode mode); + static QString getComponentModeString(uint32_t mode); + static QString getComponentModeAsString(uint32_t mode); + + const auto findComponent(const QString& mode); public: float getMaxDimension() const { return glm::compMax(_dimensions); } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ff1b65db15..a0eaefd43e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2281,22 +2281,24 @@ bool EntityTree::readFromMap(QVariantMap& map) { properties.setOwningAvatarID(myNodeID); } - // TEMPORARY fix for older content not containing these fields - if (!entityMap.contains("keyLightMode")) { - properties.setKeyLightMode(COMPONENT_MODE_ENABLED); - } - - if (!entityMap.contains("skyboxMode")) { - if (entityMap.contains("backgroundMode") && properties.getBackgroundModeAsString() == "inherit") { - // The content creator has set the combo to NOTHING - this is actually inherit - properties.setSkyboxMode(COMPONENT_MODE_INHERIT); - } else { - properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + // TEMPORARY fix for older content not containing these fields in the zones + if (properties.getType() == EntityTypes::EntityType::Zone) { + if (!entityMap.contains("keyLightMode")) { + properties.setKeyLightMode(COMPONENT_MODE_ENABLED); } - } - if (!entityMap.contains("ambientLightMode")) { - properties.setAmbientLightMode(COMPONENT_MODE_ENABLED); + if (!entityMap.contains("skyboxMode")) { + if (entityMap.contains("backgroundMode") && properties.getBackgroundModeAsString() == "inherit") { + // The content creator has set the combo to NOTHING - this is actually inherit + properties.setSkyboxMode(COMPONENT_MODE_INHERIT); + } else { + properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + } + } + + if (!entityMap.contains("ambientLightMode")) { + properties.setAmbientLightMode(COMPONENT_MODE_ENABLED); + } } EntityItemPointer entity = addEntity(entityItemID, properties); From 635fd2d0cf84687248ddf58f50c0fe6622274d7b Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 5 Jan 2018 12:48:21 -0800 Subject: [PATCH 62/90] Use older C++ version for Ubuntu --- libraries/entities/src/EntityItemProperties.cpp | 2 +- libraries/entities/src/EntityItemProperties.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 76a591f20a..90470adcf0 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -231,7 +231,7 @@ QString EntityItemProperties::getComponentModeString(uint32_t mode) { } } -const auto EntityItemProperties::findComponent(const QString& mode) { +const std::array::const_iterator EntityItemProperties::findComponent(const QString& mode) { return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { return (pair.second == mode); }); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 467a89166c..5a210e54ce 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -266,7 +266,7 @@ public: static QString getComponentModeString(uint32_t mode); static QString getComponentModeAsString(uint32_t mode); - const auto findComponent(const QString& mode); + const std::array::const_iterator findComponent(const QString& mode); public: float getMaxDimension() const { return glm::compMax(_dimensions); } From d6451632949858defb4a1b8dd492e2ecbfce07b0 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 5 Jan 2018 13:16:02 -0800 Subject: [PATCH 63/90] Fixed gcc warning. --- libraries/entities/src/EntityItemProperties.cpp | 2 +- libraries/entities/src/EntityItemProperties.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 90470adcf0..cfbfdd0274 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -231,7 +231,7 @@ QString EntityItemProperties::getComponentModeString(uint32_t mode) { } } -const std::array::const_iterator EntityItemProperties::findComponent(const QString& mode) { +std::array::const_iterator EntityItemProperties::findComponent(const QString& mode) { return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { return (pair.second == mode); }); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 5a210e54ce..0240cee44a 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -266,7 +266,7 @@ public: static QString getComponentModeString(uint32_t mode); static QString getComponentModeAsString(uint32_t mode); - const std::array::const_iterator findComponent(const QString& mode); + std::array::const_iterator findComponent(const QString& mode); public: float getMaxDimension() const { return glm::compMax(_dimensions); } From 579aec71184ca4682ea250d2bd0722513525ea01 Mon Sep 17 00:00:00 2001 From: "nissim.hadar" Date: Sat, 6 Jan 2018 01:37:23 -0800 Subject: [PATCH 64/90] Only check for zone fields. --- libraries/entities/src/EntityTree.cpp | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ff1b65db15..a0eaefd43e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2281,22 +2281,24 @@ bool EntityTree::readFromMap(QVariantMap& map) { properties.setOwningAvatarID(myNodeID); } - // TEMPORARY fix for older content not containing these fields - if (!entityMap.contains("keyLightMode")) { - properties.setKeyLightMode(COMPONENT_MODE_ENABLED); - } - - if (!entityMap.contains("skyboxMode")) { - if (entityMap.contains("backgroundMode") && properties.getBackgroundModeAsString() == "inherit") { - // The content creator has set the combo to NOTHING - this is actually inherit - properties.setSkyboxMode(COMPONENT_MODE_INHERIT); - } else { - properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + // TEMPORARY fix for older content not containing these fields in the zones + if (properties.getType() == EntityTypes::EntityType::Zone) { + if (!entityMap.contains("keyLightMode")) { + properties.setKeyLightMode(COMPONENT_MODE_ENABLED); } - } - if (!entityMap.contains("ambientLightMode")) { - properties.setAmbientLightMode(COMPONENT_MODE_ENABLED); + if (!entityMap.contains("skyboxMode")) { + if (entityMap.contains("backgroundMode") && properties.getBackgroundModeAsString() == "inherit") { + // The content creator has set the combo to NOTHING - this is actually inherit + properties.setSkyboxMode(COMPONENT_MODE_INHERIT); + } else { + properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + } + } + + if (!entityMap.contains("ambientLightMode")) { + properties.setAmbientLightMode(COMPONENT_MODE_ENABLED); + } } EntityItemPointer entity = addEntity(entityItemID, properties); From d2ac27f1b883a528190ae68ceaea0c83a9e5e2de Mon Sep 17 00:00:00 2001 From: "nissim.hadar" Date: Sat, 6 Jan 2018 01:48:58 -0800 Subject: [PATCH 65/90] Code clean-up --- .../entities/src/EntityItemProperties.cpp | 63 +++++++------------ libraries/entities/src/EntityItemProperties.h | 11 ++++ 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 0a53f96a59..cfbfdd0274 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -208,20 +208,18 @@ void EntityItemProperties::setBackgroundModeFromString(const QString& background } } -using ComponentPair = std::pair; -const std::array COMPONENT_MODES = { { - ComponentPair{ COMPONENT_MODE_INHERIT,{ "inherit" } }, - ComponentPair{ COMPONENT_MODE_DISABLED,{ "disabled" } }, - ComponentPair{ COMPONENT_MODE_ENABLED,{ "enabled" } } -} }; - -QString EntityItemProperties::getHazeModeAsString() const { - // return "inherit" if _hazeMode is not valid - if (_hazeMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_hazeMode].second; +QString EntityItemProperties::getComponentModeAsString(uint32_t mode) { + // return "inherit" if mode is not valid + if (mode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[mode].second; } else { return COMPONENT_MODES[COMPONENT_MODE_INHERIT].second; } + +} + +QString EntityItemProperties::getHazeModeAsString() const { + return getComponentModeAsString(_hazeMode); } QString EntityItemProperties::getComponentModeString(uint32_t mode) { @@ -233,10 +231,14 @@ QString EntityItemProperties::getComponentModeString(uint32_t mode) { } } -void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == hazeMode); +std::array::const_iterator EntityItemProperties::findComponent(const QString& mode) { + return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { + return (pair.second == mode); }); +} + +void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { + auto result = findComponent(hazeMode); if (result != COMPONENT_MODES.end()) { _hazeMode = result->first; @@ -245,18 +247,11 @@ void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { } QString EntityItemProperties::getKeyLightModeAsString() const { - // return "enabled" if _keyLightMode is not valid - if (_keyLightMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_keyLightMode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } + return getComponentModeAsString(_keyLightMode); } void EntityItemProperties::setKeyLightModeFromString(const QString& keyLightMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == keyLightMode); - }); + auto result = findComponent(keyLightMode); if (result != COMPONENT_MODES.end()) { _keyLightMode = result->first; @@ -265,18 +260,11 @@ void EntityItemProperties::setKeyLightModeFromString(const QString& keyLightMode } QString EntityItemProperties::getAmbientLightModeAsString() const { - // return "enabled" if _ambientLightMode is not valid - if (_ambientLightMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_ambientLightMode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } + return getComponentModeAsString(_ambientLightMode); } void EntityItemProperties::setAmbientLightModeFromString(const QString& ambientLightMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == ambientLightMode); - }); + auto result = findComponent(ambientLightMode); if (result != COMPONENT_MODES.end()) { _ambientLightMode = result->first; @@ -285,18 +273,11 @@ void EntityItemProperties::setAmbientLightModeFromString(const QString& ambientL } QString EntityItemProperties::getSkyboxModeAsString() const { - // return "enabled" if _skyboxMode is not valid - if (_skyboxMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_skyboxMode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_ENABLED].second; - } + return getComponentModeAsString(_skyboxMode); } void EntityItemProperties::setSkyboxModeFromString(const QString& skyboxMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == skyboxMode); - }); + auto result = findComponent(skyboxMode); if (result != COMPONENT_MODES.end()) { _skyboxMode = result->first; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 587b9189a1..0240cee44a 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -47,6 +47,13 @@ const quint64 UNKNOWN_CREATED_TIME = 0; +using ComponentPair = std::pair; +const std::array COMPONENT_MODES = { { + ComponentPair { COMPONENT_MODE_INHERIT, { "inherit" } }, + ComponentPair { COMPONENT_MODE_DISABLED, { "disabled" } }, + ComponentPair { COMPONENT_MODE_ENABLED, { "enabled" } } +} }; + /// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an /// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete /// set of entity item properties via JavaScript hashes/QScriptValues @@ -255,7 +262,11 @@ public: DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS); static QString getBackgroundModeString(BackgroundMode mode); + static QString getComponentModeString(uint32_t mode); + static QString getComponentModeAsString(uint32_t mode); + + std::array::const_iterator findComponent(const QString& mode); public: float getMaxDimension() const { return glm::compMax(_dimensions); } From 33cc17a52f89b00e232e3b3a5cb215a3636e9fe2 Mon Sep 17 00:00:00 2001 From: "nissim.hadar" Date: Sat, 6 Jan 2018 02:27:49 -0800 Subject: [PATCH 66/90] Removing unused Background mode. --- libraries/entities/src/EntityTree.cpp | 12 +++++------- libraries/entities/src/ZoneEntityItem.cpp | 3 --- libraries/entities/src/ZoneEntityItem.h | 2 -- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index a0eaefd43e..1efbe3ae0c 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2287,13 +2287,11 @@ bool EntityTree::readFromMap(QVariantMap& map) { properties.setKeyLightMode(COMPONENT_MODE_ENABLED); } - if (!entityMap.contains("skyboxMode")) { - if (entityMap.contains("backgroundMode") && properties.getBackgroundModeAsString() == "inherit") { - // The content creator has set the combo to NOTHING - this is actually inherit - properties.setSkyboxMode(COMPONENT_MODE_INHERIT); - } else { - properties.setSkyboxMode(COMPONENT_MODE_ENABLED); - } + if (entityMap.contains("backgroundMode") && properties.getBackgroundModeAsString() == "skybox") { + properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + } else { + // The content creator has set the combo to NOTHING - this is actually inherit + properties.setSkyboxMode(COMPONENT_MODE_INHERIT); } if (!entityMap.contains("ambientLightMode")) { diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index f2fb480353..38401de82e 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -42,8 +42,6 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en _shapeType = DEFAULT_SHAPE_TYPE; _compoundShapeURL = DEFAULT_COMPOUND_SHAPE_URL; - - _backgroundMode = BACKGROUND_MODE_INHERIT; } EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { @@ -285,7 +283,6 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " _backgroundMode:" << EntityItemProperties::getBackgroundModeString(_backgroundMode); qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getComponentModeString(_hazeMode); qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getComponentModeString(_keyLightMode); qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeString(_ambientLightMode); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index aca7a0fd1b..6aae7bfbc3 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -128,8 +128,6 @@ protected: ShapeType _shapeType = DEFAULT_SHAPE_TYPE; QString _compoundShapeURL; - BackgroundMode _backgroundMode { BACKGROUND_MODE_INHERIT }; - // The following 3 values are the defaults for zone creation uint32_t _keyLightMode { COMPONENT_MODE_INHERIT }; uint32_t _skyboxMode { COMPONENT_MODE_INHERIT }; From 1acf7bae204de31c85f5c649b01d1bdaa61c7af6 Mon Sep 17 00:00:00 2001 From: "nissim.hadar" Date: Sat, 6 Jan 2018 23:24:28 -0800 Subject: [PATCH 67/90] Removed reference to BackgroundMode. --- libraries/entities/src/EntityTree.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 1efbe3ae0c..f53ceeee2e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2287,11 +2287,8 @@ bool EntityTree::readFromMap(QVariantMap& map) { properties.setKeyLightMode(COMPONENT_MODE_ENABLED); } - if (entityMap.contains("backgroundMode") && properties.getBackgroundModeAsString() == "skybox") { + if (!entityMap.contains("skyboxMode")) { properties.setSkyboxMode(COMPONENT_MODE_ENABLED); - } else { - // The content creator has set the combo to NOTHING - this is actually inherit - properties.setSkyboxMode(COMPONENT_MODE_INHERIT); } if (!entityMap.contains("ambientLightMode")) { From 8206ae6ffb41c7e87f671fa7179a4cec781cc760 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Sun, 7 Jan 2018 13:50:42 -0800 Subject: [PATCH 68/90] Removed BackgroundMode packet type. --- .../src/RenderableZoneEntityItem.cpp | 4 -- .../src/RenderableZoneEntityItem.h | 3 -- .../entities/src/EntityItemProperties.cpp | 38 ------------------- libraries/entities/src/EntityItemProperties.h | 5 --- libraries/octree/src/OctreePacketData.h | 2 - 5 files changed, 52 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 8cc4a0194f..9f895ad125 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -488,10 +488,6 @@ void ZoneEntityRenderer::updateSkyboxMap() { } } -void ZoneEntityRenderer::setBackgroundMode(BackgroundMode mode) { - _backgroundMode = mode; -} - void ZoneEntityRenderer::setHazeMode(ComponentMode mode) { _hazeMode = mode; } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 37b40e01b6..744f1823fc 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -54,7 +54,6 @@ private: void updateSkyboxMap(); void setAmbientURL(const QString& ambientUrl); void setSkyboxURL(const QString& skyboxUrl); - void setBackgroundMode(BackgroundMode mode); void setHazeMode(ComponentMode mode); void setKeyLightMode(ComponentMode mode); @@ -89,8 +88,6 @@ private: const model::SunSkyStagePointer _background{ std::make_shared() }; const model::HazePointer _haze{ std::make_shared() }; - BackgroundMode _backgroundMode{ BACKGROUND_MODE_INHERIT }; - ComponentMode _keyLightMode { COMPONENT_MODE_INHERIT }; ComponentMode _ambientLightMode { COMPONENT_MODE_INHERIT }; ComponentMode _skyboxMode { COMPONENT_MODE_INHERIT }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index cfbfdd0274..d118747fa1 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -184,30 +184,6 @@ void EntityItemProperties::setShapeTypeFromString(const QString& shapeName) { } } -using BackgroundPair = std::pair; -const std::array BACKGROUND_MODES = { { - BackgroundPair { BACKGROUND_MODE_INHERIT, { "inherit" } }, - BackgroundPair { BACKGROUND_MODE_SKYBOX, { "skybox" } } -} }; - -QString EntityItemProperties::getBackgroundModeAsString() const { - return BACKGROUND_MODES[_backgroundMode].second; -} - -QString EntityItemProperties::getBackgroundModeString(BackgroundMode mode) { - return BACKGROUND_MODES[mode].second; -} - -void EntityItemProperties::setBackgroundModeFromString(const QString& backgroundMode) { - auto result = std::find_if(BACKGROUND_MODES.begin(), BACKGROUND_MODES.end(), [&](const BackgroundPair& pair) { - return (pair.second == backgroundMode); - }); - if (result != BACKGROUND_MODES.end()) { - _backgroundMode = result->first; - _backgroundModeChanged = true; - } -} - QString EntityItemProperties::getComponentModeAsString(uint32_t mode) { // return "inherit" if mode is not valid if (mode < COMPONENT_MODE_ITEM_COUNT) { @@ -215,7 +191,6 @@ QString EntityItemProperties::getComponentModeAsString(uint32_t mode) { } else { return COMPONENT_MODES[COMPONENT_MODE_INHERIT].second; } - } QString EntityItemProperties::getHazeModeAsString() const { @@ -370,7 +345,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); CHECK_PROPERTY_CHANGE(PROP_NAME, name); - CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode); CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); CHECK_PROPERTY_CHANGE(PROP_KEY_LIGHT_MODE, keyLightMode); @@ -602,8 +576,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool _keyLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _ambientLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BACKGROUND_MODE, backgroundMode, getBackgroundModeAsString()); - _stage.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); @@ -803,7 +775,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(backgroundMode, BackgroundMode); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(hazeMode, HazeMode); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(keyLightMode, KeyLightMode); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(ambientLightMode, AmbientLightMode); @@ -965,7 +936,6 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(name); COPY_PROPERTY_IF_CHANGED(collisionSoundURL); - COPY_PROPERTY_IF_CHANGED(backgroundMode); COPY_PROPERTY_IF_CHANGED(hazeMode); COPY_PROPERTY_IF_CHANGED(keyLightMode); COPY_PROPERTY_IF_CHANGED(ambientLightMode); @@ -1484,8 +1454,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)properties.getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL()); - APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)properties.getBackgroundMode()); - _staticSkybox.setProperties(properties); _staticSkybox.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -1842,7 +1810,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); @@ -2086,7 +2053,6 @@ void EntityItemProperties::markAllChanged() { _ambientLight.markAllChanged(); _skybox.markAllChanged(); - _backgroundModeChanged = true; _hazeModeChanged = true; _animation.markAllChanged(); @@ -2429,10 +2395,6 @@ QList EntityItemProperties::listChangedProperties() { out += "staticCertificateVersion"; } - if (backgroundModeChanged()) { - out += "backgroundMode"; - } - if (hazeModeChanged()) { out += "hazeMode"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 0240cee44a..587a72fb04 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -185,7 +185,6 @@ public: DEFINE_PROPERTY_REF(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray, PolyVoxEntityItem::DEFAULT_VOXEL_DATA); DEFINE_PROPERTY_REF(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t, PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE); DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME); - DEFINE_PROPERTY_REF_ENUM(PROP_BACKGROUND_MODE, BackgroundMode, backgroundMode, BackgroundMode, BACKGROUND_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup); DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); @@ -261,8 +260,6 @@ public: DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS); - static QString getBackgroundModeString(BackgroundMode mode); - static QString getComponentModeString(uint32_t mode); static QString getComponentModeAsString(uint32_t mode); @@ -494,8 +491,6 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, HazeMode, hazeMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, KeyLightMode, keyLightMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, ""); diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 09eb134124..ef4e98798f 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -258,7 +257,6 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, rgbColor& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::quat& result) { int bytes = unpackOrientationQuatFromBytes(dataBytes, result); return bytes; } static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int unpackDataFromBytes(const unsigned char* dataBytes, BackgroundMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, QString& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result); static int unpackDataFromBytes(const unsigned char* dataBytes, xColor& result); From 8e793e790a16da8ea3aaf287fa13e351037a26dd Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Sun, 7 Jan 2018 14:11:26 -0800 Subject: [PATCH 69/90] Deleted unused include file. --- .../entities/src/EntityItemProperties.cpp | 1 - libraries/shared/src/BackgroundMode.h | 23 ------------------- 2 files changed, 24 deletions(-) delete mode 100644 libraries/shared/src/BackgroundMode.h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index d118747fa1..f3ceeb208d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1134,7 +1134,6 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray); ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); - ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_MODE, BackgroundMode, backgroundMode, BackgroundMode); ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); diff --git a/libraries/shared/src/BackgroundMode.h b/libraries/shared/src/BackgroundMode.h deleted file mode 100644 index 0e0d684e62..0000000000 --- a/libraries/shared/src/BackgroundMode.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// BackgroundMode.h -// libraries/physics/src -// -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_BackgroundMode_h -#define hifi_BackgroundMode_h - -enum BackgroundMode { - BACKGROUND_MODE_INHERIT, - BACKGROUND_MODE_SKYBOX, - - BACKGROUND_MODE_ITEM_COUNT, -}; - - -#endif // hifi_BackgroundMode_h - From 93fffb4bbe09b1acd7378f5f131196987209d3b4 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Sun, 7 Jan 2018 14:21:22 -0800 Subject: [PATCH 70/90] Removed PROP_BACKGROUND_MODE. --- libraries/entities/src/EntityPropertyFlags.h | 1 - libraries/entities/src/ZoneEntityItem.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index e78942dc55..f216bf46d0 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -255,7 +255,6 @@ enum EntityPropertyList { PROP_STAGE_DAY = PROP_LINEAR_ATTENUATION_UNUSED, PROP_STAGE_HOUR = PROP_QUADRATIC_ATTENUATION_UNUSED, PROP_STAGE_AUTOMATIC_HOURDAY = PROP_ANIMATION_FRAME_INDEX, - PROP_BACKGROUND_MODE = PROP_MODEL_URL, PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, PROP_SKYBOX_URL = PROP_ANIMATION_FPS, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 38401de82e..146e986bc2 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -218,7 +218,6 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_SHAPE_TYPE; requestedProperties += PROP_COMPOUND_SHAPE_URL; - requestedProperties += PROP_BACKGROUND_MODE; withReadLock([&] { requestedProperties += _skyboxProperties.getEntityProperties(params); From cdd62c7119b161d46e09d0385c0adc0c6ef8bbe5 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 8 Jan 2018 11:29:41 -0800 Subject: [PATCH 71/90] 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 d0c0d6a388298ee38c6293b98ac8113e86d68bcf Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 8 Jan 2018 11:50:28 -0800 Subject: [PATCH 72/90] Set skybox mode as per legacy background mode if needed. --- 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 11927c4dca..07d004358a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2288,7 +2288,12 @@ bool EntityTree::readFromMap(QVariantMap& map) { } if (!entityMap.contains("skyboxMode")) { - properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + 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" + properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + } } if (!entityMap.contains("ambientLightMode")) { From 8e03ecedb9dacdd6e5c6d76127d198092734a27b Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 8 Jan 2018 11:50:49 -0800 Subject: [PATCH 73/90] Added user data for lat/lon. --- scripts/developer/sunModel.js | 67 +++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/scripts/developer/sunModel.js b/scripts/developer/sunModel.js index 2343e3da7f..f36f0fadc2 100644 --- a/scripts/developer/sunModel.js +++ b/scripts/developer/sunModel.js @@ -19,14 +19,6 @@ return angle_rads * (180.0 / Math.PI); } - // Parameters - var latitude_degs = 47.751033; - var longitude_degs = -122.228176; - - // These are used a lot - var latitude = toRadians(latitude_degs); - var longitude = toRadians(longitude_degs); - // Code to check if Daylight Savings is active Date.prototype.stdTimezoneOffset = function() { var fy = this.getFullYear(); @@ -55,9 +47,10 @@ }; // The Julian Date is the number of days (fractional) that have elapsed since Jan 1st, 4713 BC - function getJulianDate(dateTime) { + // See https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html + function getJulianDay(dateTime) { var month = dateTime.getMonth() + 1; - var day = dateTime.getDate() + 1; + var day = dateTime.getDate() + 1; var year = dateTime.getFullYear(); if (month <= 2) { @@ -150,6 +143,11 @@ } function calcEquationOfTime(t) { + // Converts from "mean" solar day (i.e. days that are exactly 24 hours) + // to apparent solar day (as observed) + // This is essentially the east-west component of the analemma. + // + // This is caused by 2 effects: the obliquity of the ecliptic, the eccentricity of earth's orbit var epsilon = calcObliquityCorrection(t); var l0 = calcGeomMeanLongSun(t); var e = calcEccentricityEarthOrbit(t); @@ -164,7 +162,9 @@ var sin4l0 = Math.sin(4.0 * toRadians(l0)); var sin2m = Math.sin(2.0 * toRadians(m)); - var Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m; + var Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - + 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m; + return toDegrees(Etime) * 4.0; // in minutes of time } @@ -181,22 +181,53 @@ var R = (1.000001018 * (1 - e * e)) / (1 + e * Math.cos(toRadians(v))); return R; // in AUs } - + + function parseJSON(json) { + try { + return JSON.parse(json); + } catch (e) { + return undefined; + } + } + var COMPUTATION_CYCLE = 5000; // Run every 5 seconds - this.preload = function(entityID) { // You don't have the entityID before the preload + this.preload = function(entityID) { // We don't have the entityID before the preload + // Define user data + var userDataProperties = { + "userData": "{ \"latitude\": 47.0, \"longitude\": 122.0 }" + }; + Entities.editEntity(entityID, userDataProperties); + Script.setInterval( function() { + // Read back user data + var userData = Entities.getEntityProperties(entityID, 'userData').userData; + var data = parseJSON(userData); + + var latitude_degs = data.latitude; + var longitude_degs = data.longitude; + + // These are used a lot + var latitude = toRadians(latitude_degs); + var longitude = toRadians(longitude_degs); + var dateTime = new Date(); - var julianDay = getJulianDate(dateTime); + var julianDay = getJulianDay(dateTime); var localTimeMinutes = getMinutes(dateTime); var timeZone = -dateTime.getTimezoneOffset() / 60; var totalTime = julianDay + localTimeMinutes/1440.0 - timeZone / 24.0; - var julianCentralTime = (julianDay - 2451545.0)/36525.0; - var eqTime = calcEquationOfTime(julianCentralTime) - var theta_rads = toRadians(calcSunDeclination(julianCentralTime)); + + // J2000.0 is the epoch starting Jan 1st, 2000 (noon), expressed as a Julian day + var J2000 = 2451545.0 + + // Number of years that have passed since J2000.0 + var julianDayModified = (J2000 - 2451545.0)/36525.0; + + var eqTime = calcEquationOfTime(julianDayModified) + var theta_rads = toRadians(calcSunDeclination(julianDayModified)); var solarTimeFix = eqTime + 4.0 * longitude_degs - 60.0 * timeZone; - var earthRadVec = calcSunRadVector(julianCentralTime); + var earthRadVec = calcSunRadVector(julianDayModified); var trueSolarTime = localTimeMinutes + solarTimeFix; while (trueSolarTime > 1440) { From dcfceab0789d5b7379f200452cfc5adbe923b7a1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 8 Jan 2018 12:03:56 -0800 Subject: [PATCH 74/90] 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 0a21e5de047150266b0001028b007fbadfa2bd8d Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 8 Jan 2018 12:07:21 -0800 Subject: [PATCH 75/90] Removed _backgroundPropertiesChanged --- libraries/entities/src/ZoneEntityItem.cpp | 1 - libraries/entities/src/ZoneEntityItem.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 146e986bc2..27b2122511 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -355,7 +355,6 @@ void ZoneEntityItem::resetRenderingPropertiesChanged() { withWriteLock([&] { _keyLightPropertiesChanged = false; _ambientLightPropertiesChanged = false; - _backgroundPropertiesChanged = false; _skyboxPropertiesChanged = false; _hazePropertiesChanged = false; _stagePropertiesChanged = false; diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 6aae7bfbc3..fc79db4311 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -96,7 +96,6 @@ public: bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; } bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; } - bool backgroundPropertiesChanged() const { return _backgroundPropertiesChanged; } bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; } bool hazePropertiesChanged() const { @@ -146,7 +145,6 @@ protected: // Dirty flags turn true when either keylight properties is changing values. bool _keyLightPropertiesChanged { false }; bool _ambientLightPropertiesChanged { false }; - bool _backgroundPropertiesChanged{ false }; bool _skyboxPropertiesChanged { false }; bool _hazePropertiesChanged{ false }; bool _stagePropertiesChanged { false }; From 452c16e7066aae0a1cc4e6c2745ebf96881093db Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 8 Jan 2018 12:11:36 -0800 Subject: [PATCH 76/90] Removed backgroundPropertiesChanged --- libraries/entities-renderer/src/RenderableZoneEntityItem.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 9f895ad125..ae00f168c6 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -206,7 +206,6 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen // resulting in a lost update bool keyLightChanged = entity->keyLightPropertiesChanged(); bool ambientLightChanged = entity->ambientLightPropertiesChanged(); - bool backgroundChanged = entity->backgroundPropertiesChanged(); bool skyboxChanged = entity->skyboxPropertiesChanged(); bool hazeChanged = entity->hazePropertiesChanged(); @@ -250,7 +249,7 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateAmbientLightFromEntity(entity); } - if (backgroundChanged || skyboxChanged) { + if (skyboxChanged) { updateKeyBackgroundFromEntity(entity); } @@ -274,7 +273,6 @@ ItemKey ZoneEntityRenderer::getKey() { bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (entity->keyLightPropertiesChanged() || entity->ambientLightPropertiesChanged() || - entity->backgroundPropertiesChanged() || entity->hazePropertiesChanged() || entity->skyboxPropertiesChanged()) { From 611f25d542d4cf61f4e372f287700cfcede96046 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 8 Jan 2018 13:07:00 -0800 Subject: [PATCH 77/90] 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 78/90] 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 4cf40554c037abfe2252eded52fdde4184f29a19 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 8 Jan 2018 16:06:31 -0800 Subject: [PATCH 79/90] don't call setQmlTabletRoot twice --- interface/src/ui/overlays/Web3DOverlay.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index a5da5e99b6..45f9f8e276 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -222,10 +222,6 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - // mark the TabletProxy object as cpp ownership. - QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); - _webSurface->getSurfaceContext()->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); // Override min fps for tablet UI, for silky smooth scrolling setMaxFPS(90); } From 0f6b93eaea5ff25a49b07e63f6cfd38d78f24ed9 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 5 Jan 2018 23:27:32 +0300 Subject: [PATCH 80/90] FB11123 Make the certificate GOLD (and change title to Proof of Provenance License) --- .../InspectionCertificate.qml | 26 +++++++++--------- .../inspectionCertificate/images/cert-bg.jpg | Bin 64886 -> 64011 bytes 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 28c32c59de..c331532f5e 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -145,7 +145,7 @@ Rectangle { // Title text RalewayRegular { id: popText; - text: "PROOF OF PURCHASE"; + text: "Proof of Provenance"; // Text size size: 16; // Anchors @@ -155,7 +155,7 @@ Rectangle { anchors.right: titleBarText.right; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.darkGray; } // @@ -182,7 +182,7 @@ Rectangle { anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.darkGray; } RalewaySemiBold { id: itemName; @@ -196,7 +196,7 @@ Rectangle { anchors.right: itemNameHeader.right; height: paintedHeight; // Style - color: hifi.colors.blueAccent; + color: hifi.colors.white; elide: Text.ElideRight; MouseArea { anchors.fill: parent; @@ -205,7 +205,7 @@ Rectangle { sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); } onEntered: itemName.color = hifi.colors.blueHighlight; - onExited: itemName.color = hifi.colors.blueAccent; + onExited: itemName.color = hifi.colors.white; } } @@ -223,7 +223,7 @@ Rectangle { anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.lightGray; + color: hifi.colors.darkGray; } RalewayRegular { id: ownedBy; @@ -236,7 +236,7 @@ Rectangle { anchors.left: ownedByHeader.left; height: paintedHeight; // Style - color: hifi.colors.darkGray; + color: hifi.colors.white; elide: Text.ElideRight; } AnonymousProRegular { @@ -252,7 +252,7 @@ Rectangle { anchors.leftMargin: 6; anchors.right: ownedByHeader.right; // Style - color: hifi.colors.lightGray; + color: hifi.colors.white; elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter; } @@ -271,7 +271,7 @@ Rectangle { anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.lightGray; + color: hifi.colors.darkGray; } AnonymousProRegular { id: edition; @@ -285,7 +285,7 @@ Rectangle { anchors.right: editionHeader.right; height: paintedHeight; // Style - color: hifi.colors.darkGray; + color: hifi.colors.white; } RalewayRegular { @@ -302,7 +302,7 @@ Rectangle { anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.lightGray; + color: hifi.colors.darkGray; } AnonymousProRegular { id: dateOfPurchase; @@ -316,7 +316,7 @@ Rectangle { anchors.right: dateOfPurchaseHeader.right; height: paintedHeight; // Style - color: hifi.colors.darkGray; + color: hifi.colors.white; } RalewayRegular { @@ -349,7 +349,7 @@ Rectangle { // "Cancel" button HifiControlsUit.Button { - color: hifi.buttons.noneBorderless; + color: hifi.buttons.noneBorderlessWhite; colorScheme: hifi.colorSchemes.light; anchors.top: parent.top; anchors.left: parent.left; diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg index 9cecc79869f0fd8e4a856d47ef0793b66c5bd775..b39a55e4e83f3290d7befdfeb1055cf9b73f4bfc 100644 GIT binary patch literal 64011 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 literal 64886 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%0ALDH9;7k>;jA5WiP#)y1LdYG*LxpkQs&Rb^< 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
  • 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 From 4f5727e6b0c143a400c6e0be063fd91eb6eb8c4a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 8 Jan 2018 15:25:16 -0800 Subject: [PATCH 81/90] 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 82/90] 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 83/90] 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 84/90] 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 85/90] 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 86/90] 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 87/90] 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 88/90] 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 89/90] 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 90/90] 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);