From a51ba5f4a9f8523f44bc676547c2fc1f5ca05036 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Tue, 1 Nov 2022 08:52:51 +0000 Subject: [PATCH 01/10] Circleci manual holds (#1152) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(circleci): introduces manual holds to prevent resource wastage We were building all commits unnecessarily. The logic was not consistent for versioning and when to build and when to publish. This PR addresses these issues. * Replaces `should-build` with `build-approval` * Replace `should-publish` with `publish-approval` * Clean up image tagging logic - tags as latest when version is semver or semver-alpha.build_number - other commits aren't tagged as latest, and instead tagged with their branch name - move naming logic to common.sh script * Push each tag individually to avoid pollution - docker engine used is shared amongst all runs of speckle-server pipeline, so pushing all tags indiscrimanately pushes images built on other CircleCI runs 😬 * Improve logging * Do not require approval for any tagged commit --- .circleci/build.sh | 6 -- .circleci/common.sh | 8 +++ .circleci/config.yml | 111 +++++++++++------------------------- .circleci/get_version.sh | 8 +-- .circleci/publish.sh | 35 +++++++----- .circleci/should_build.sh | 17 ------ .circleci/should_publish.sh | 17 ------ 7 files changed, 64 insertions(+), 138 deletions(-) delete mode 100755 .circleci/should_build.sh delete mode 100755 .circleci/should_publish.sh diff --git a/.circleci/build.sh b/.circleci/build.sh index 18ea3ba6e..7071142a0 100755 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -1,12 +1,6 @@ #!/bin/bash set -eo pipefail -SHOULD_BUILD="${SHOULD_BUILD:-false}" -if [[ "${SHOULD_BUILD}" != "true" ]]; then - echo "Not building as the SHOULD_BUILD environment variable is not 'true'." - exit 0 -fi - # enables building the test-deployment container with the same script # defaults to packages for minimal intervention in the ci config FOLDER="${FOLDER:-packages}" diff --git a/.circleci/common.sh b/.circleci/common.sh index 4abeb82fc..4fa231035 100755 --- a/.circleci/common.sh +++ b/.circleci/common.sh @@ -5,3 +5,11 @@ DOCKER_IMAGE_TAG="speckle/speckle-${SPECKLE_SERVER_PACKAGE}" IMAGE_VERSION_TAG="${IMAGE_VERSION_TAG:-${CIRCLE_SHA1}}" # shellcheck disable=SC2034,SC2086 DOCKER_FILE_NAME="$(echo ${DOCKER_IMAGE_TAG}_${IMAGE_VERSION_TAG} | sed -e 's/[^A-Za-z0-9._-]/_/g')" +# shellcheck disable=SC2068,SC2046 +LAST_RELEASE="$(git describe --always --tags $(git rev-list --tags) | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)" +# shellcheck disable=SC2034 +NEXT_RELEASE="$(echo "${LAST_RELEASE}" | python -c "parts = input().split('.'); parts[-1] = str(int(parts[-1])+1); print('.'.join(parts))")" +# shellcheck disable=SC2034 +BRANCH_NAME_TRUNCATED="$(echo "${CIRCLE_BRANCH}" | cut -c -50 | sed 's/[^a-zA-Z0-9_.-]/_/g')" # docker has a 128 character tag limit, so ensuring the branch name will be short enough +# shellcheck disable=SC2034 +COMMIT_SHA1_TRUNCATED="$(echo "${CIRCLE_SHA1}" | cut -c -7)" diff --git a/.circleci/config.yml b/.circleci/config.yml index 7cf83e611..4f9c3efed 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,22 +6,24 @@ workflows: test-build: jobs: - test-server: - filters: &filters-everything + filters: &filters-allow-all tags: # run tests for any commit on any branch, including any tags only: /.*/ - get-version: - filters: *filters-everything + filters: *filters-allow-all - - should-build: - filters: *filters-everything - - - should-publish: - filters: *filters-everything + - build-approval: + type: approval + filters: &filters-ignore-main-branch-all-tags + branches: + ignore: main + tags: + ignore: /.*/ - pre-commit: - filters: *filters-everything + filters: *filters-allow-all - docker-build-server: filters: &filters-build @@ -30,47 +32,51 @@ workflows: requires: - test-server - get-version - - should-build + - build-approval - docker-build-frontend: filters: *filters-build requires: - get-version - - should-build + - build-approval - docker-build-webhooks: filters: *filters-build requires: - get-version - test-server - - should-build + - build-approval - docker-build-file-imports: filters: *filters-build requires: - get-version - test-server - - should-build + - build-approval - docker-build-previews: filters: *filters-build requires: - get-version - test-server - - should-build + - build-approval - docker-build-test-container: filters: *filters-build requires: - get-version - test-server - - should-build + - build-approval - docker-build-monitor-container: filters: *filters-build requires: - get-version - - should-build + - build-approval + + - publish-approval: + type: approval + filters: *filters-ignore-main-branch-all-tags - docker-publish-server: context: &docker-hub-context @@ -82,7 +88,7 @@ workflows: only: /.*/ requires: - get-version - - should-publish + - publish-approval - docker-build-server - pre-commit @@ -91,7 +97,7 @@ workflows: filters: *filters-publish requires: - get-version - - should-publish + - publish-approval - docker-build-frontend - pre-commit @@ -100,7 +106,7 @@ workflows: filters: *filters-publish requires: - get-version - - should-publish + - publish-approval - docker-build-webhooks - pre-commit @@ -109,7 +115,7 @@ workflows: filters: *filters-publish requires: - get-version - - should-publish + - publish-approval - docker-build-file-imports - pre-commit @@ -118,7 +124,7 @@ workflows: filters: *filters-publish requires: - get-version - - should-publish + - publish-approval - docker-build-previews - pre-commit @@ -127,7 +133,7 @@ workflows: filters: *filters-publish requires: - get-version - - should-publish + - publish-approval - docker-build-test-container - pre-commit @@ -136,7 +142,7 @@ workflows: filters: *filters-publish requires: - get-version - - should-publish + - publish-approval - docker-build-monitor-container - pre-commit @@ -150,7 +156,7 @@ workflows: only: &filters-tag /^[0-9]+\.[0-9]+\.[0-9]+$/ requires: - get-version - - should-publish + - publish-approval - docker-publish-server - docker-publish-frontend - docker-publish-webhooks @@ -189,47 +195,6 @@ jobs: paths: - env-vars - should-publish: - docker: - - image: cimg/base:2022.08 - working_directory: *work-dir - environment: &publishable-tags-branches - PUBLISHABLE_TAGS: '^[0-9]+\.[0-9]+\.[0-9]+$' - # £ delimited strings of regex for matches which should be published - PUBLISHABLE_BRANCHES: '^main$£^hotfix.*£^alpha.*' - steps: - - checkout - - run: mkdir -p workspace - - run: - name: determine whether to publish - command: | - echo "export SHOULD_PUBLISH=$(.circleci/should_publish.sh)" >> workspace/should-publish - - run: cat workspace/should-publish >> $BASH_ENV - - run: echo "SHOULD_PUBLISH=${SHOULD_PUBLISH}" - - persist_to_workspace: - root: workspace - paths: - - should-publish - - should-build: - docker: - - image: cimg/base:2022.08 - working_directory: *work-dir - environment: *publishable-tags-branches - steps: - - checkout - - run: mkdir -p workspace - - run: - name: determine whether to build - command: | - echo "export SHOULD_BUILD=$(.circleci/should_build.sh)" >> workspace/should-build - - run: cat workspace/should-build >> $BASH_ENV - - run: echo "SHOULD_BUILD=${SHOULD_BUILD}" - - persist_to_workspace: - root: workspace - paths: - - should-build - pre-commit: parameters: config_file: @@ -363,7 +328,7 @@ jobs: docker-build: &build-job docker: &docker-image - - image: cimg/node:16.15 + - image: cimg/python:3.9.15-node resource_class: medium working_directory: *work-dir steps: @@ -371,19 +336,13 @@ jobs: - attach_workspace: at: /tmp/ci/workspace - run: cat workspace/env-vars >> $BASH_ENV - - run: cat workspace/should-build >> $BASH_ENV - - run: - name: 'Check if should proceed' - command: | - [[ "${SHOULD_BUILD}" != true ]] && echo "Should not build, stopping" && circleci-agent step halt - echo 'Proceeding with build' - setup_remote_docker: # a weird issue with yarn installing packages throwing EPERM errors # this fixes it version: 20.10.12 docker_layer_caching: true - run: - name: Build and Publish + name: Build and Save command: ./.circleci/build.sh - persist_to_workspace: root: workspace @@ -429,7 +388,7 @@ jobs: docker-publish: &publish-job docker: &base-image - - image: cimg/base:2022.08 + - image: cimg/python:3.9.15-node resource_class: medium working_directory: *work-dir steps: @@ -437,12 +396,6 @@ jobs: - attach_workspace: at: /tmp/ci/workspace - run: cat workspace/env-vars >> $BASH_ENV - - run: cat workspace/should-publish >> $BASH_ENV - - run: - name: 'Check if should proceed' - command: | - [[ "${SHOULD_PUBLISH}" != true ]] && echo "Should not publish, stopping" && circleci-agent step halt - echo 'Proceeding with publish' - setup_remote_docker: # a weird issue with yarn installing packages throwing EPERM errors # this fixes it diff --git a/.circleci/get_version.sh b/.circleci/get_version.sh index bf98d109b..b4ebf6ec5 100755 --- a/.circleci/get_version.sh +++ b/.circleci/get_version.sh @@ -1,9 +1,9 @@ #!/bin/bash set -eo pipefail -# shellcheck disable=SC2068,SC2046 -LAST_RELEASE="$(git describe --always --tags $(git rev-list --tags) | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)" -NEXT_RELEASE="$(echo "${LAST_RELEASE}" | python -c "parts = input().split('.'); parts[-1] = str(int(parts[-1])+1); print('.'.join(parts))")" +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +# shellcheck disable=SC1090,SC1091 +source "${SCRIPT_DIR}/common.sh" if [[ "${CIRCLE_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "${CIRCLE_TAG}" @@ -15,7 +15,5 @@ if [[ "${CIRCLE_BRANCH}" == "main" ]]; then exit 0 fi -BRANCH_NAME_TRUNCATED="$(echo "${CIRCLE_BRANCH}" | cut -c -50 | sed 's/[^a-zA-Z0-9_.-]/_/g')" # docker has a 128 character tag limit, so ensuring the branch name will be short enough -COMMIT_SHA1_TRUNCATED="$(echo "${CIRCLE_SHA1}" | cut -c -7)" echo "$NEXT_RELEASE-branch.${BRANCH_NAME_TRUNCATED}.${COMMIT_SHA1_TRUNCATED}.${CIRCLE_BUILD_NUM}" exit 0 diff --git a/.circleci/publish.sh b/.circleci/publish.sh index b7a915db8..e33cd888c 100755 --- a/.circleci/publish.sh +++ b/.circleci/publish.sh @@ -1,28 +1,35 @@ #!/usr/bin/env bash set -eo pipefail -SHOULD_PUBLISH="${SHOULD_PUBLISH:-false}" - -if [[ "${SHOULD_PUBLISH}" != "true" ]]; then - echo "Not publishing as the SHOULD_PUBLISH environment variable is not 'true'." - exit 0 -fi - SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) # shellcheck disable=SC1090,SC1091 source "${SCRIPT_DIR}/common.sh" -echo "Publishing: ${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" +echo "Starting tagging & publishing of image: ${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" echo "💾 Loading image" docker load --input "/tmp/ci/workspace/${DOCKER_FILE_NAME}" -echo "🐳 Publishing image" -docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:latest" +echo "🐳 Logging into Docker" +echo "${DOCKER_REG_PASS}" | docker login -u "${DOCKER_REG_USER}" --password-stdin "${DOCKER_REG_URL}" +echo "⏫ Pushing loaded image: '${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}'" +docker push "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" -if [[ "${IMAGE_VERSION_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:2" +if [[ "${IMAGE_VERSION_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(?:-alpha\.[0-9]+)?$ ]]; then + echo "🏷 Tagging and pushing image as '${DOCKER_IMAGE_TAG}:latest'" + docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:latest" + docker push "${DOCKER_IMAGE_TAG}:latest" + + if [[ "${IMAGE_VERSION_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "🏷 Tagging and pushing image as '${DOCKER_IMAGE_TAG}:2'" + docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:2" + docker push "${DOCKER_IMAGE_TAG}:2" + fi +else + BRANCH_TAG="${NEXT_RELEASE}-branch.${BRANCH_NAME_TRUNCATED}" + echo "🏷 Tagging and pushing image as '${DOCKER_IMAGE_TAG}:${BRANCH_TAG}'" + docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:${BRANCH_TAG}" + docker push "${DOCKER_IMAGE_TAG}:${BRANCH_TAG}" fi -echo "${DOCKER_REG_PASS}" | docker login -u "${DOCKER_REG_USER}" --password-stdin "${DOCKER_REG_URL}" -docker push --all-tags "${DOCKER_IMAGE_TAG}" +echo "✅ Publishing completed." diff --git a/.circleci/should_build.sh b/.circleci/should_build.sh deleted file mode 100755 index 22d81c8fd..000000000 --- a/.circleci/should_build.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -eo pipefail - -IFS='£' read -r -a PUB_TAGS <<< "${PUBLISHABLE_TAGS}" -# shellcheck disable=SC2068 -for item in ${PUB_TAGS[@]}; do - [[ "${CIRCLE_TAG}" =~ ${item} ]] && echo "true" && exit 0 -done - -# it's on the main branch -[[ "${CIRCLE_BRANCH}" == "main" ]] && echo "true" && exit 0 - -# or it is on a branch with a Pull Request -[[ -n "${CIRCLE_PULL_REQUEST}" ]] && echo "true" && exit 0 - -echo "false" -exit 0 diff --git a/.circleci/should_publish.sh b/.circleci/should_publish.sh deleted file mode 100755 index 2774207c6..000000000 --- a/.circleci/should_publish.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -IFS='£' read -r -a PUB_TAGS <<< "${PUBLISHABLE_TAGS}" -# shellcheck disable=SC2068 -for item in ${PUB_TAGS[@]}; do - [[ "${CIRCLE_TAG}" =~ ${item} ]] && echo "true" && exit 0 -done - -IFS='£' read -r -a PUB_BRANCHES <<< "${PUBLISHABLE_BRANCHES}" -# shellcheck disable=SC2068 -for item in ${PUB_BRANCHES[@]}; do - [[ "${CIRCLE_BRANCH}" =~ ${item} ]] && echo "true" && exit 0 -done - -echo "false" -exit 0 From fa9ad5b651f72b74a81acd9dc2986429ab3a06bc Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Tue, 1 Nov 2022 11:46:25 +0200 Subject: [PATCH 02/10] #1136 Removed ctrl+click debug feature --- packages/viewer/src/modules/SpeckleRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index 7b1d581e8..11b49ddde 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -149,7 +149,7 @@ export default class SpeckleRenderer { this.input = new Input(this._renderer.domElement, InputOptionsDefault) this.input.on(ViewerEvent.ObjectClicked, this.onObjectClick.bind(this)) - this.input.on('object-clicked-debug', this.onObjectClickDebug.bind(this)) + // this.input.on('object-clicked-debug', this.onObjectClickDebug.bind(this)) this.input.on(ViewerEvent.ObjectDoubleClicked, this.onObjectDoubleClick.bind(this)) this.addDirectLights() From 1dac1e43b67936e333dda92686eb2bd4862cbe4c Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Tue, 1 Nov 2022 11:03:15 +0000 Subject: [PATCH 03/10] Fix build.sh as regex had features not available in bash regex (ere flavor) (#1166) --- .circleci/publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/publish.sh b/.circleci/publish.sh index e33cd888c..b37ab577f 100755 --- a/.circleci/publish.sh +++ b/.circleci/publish.sh @@ -15,7 +15,7 @@ echo "${DOCKER_REG_PASS}" | docker login -u "${DOCKER_REG_USER}" --password-stdi echo "⏫ Pushing loaded image: '${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}'" docker push "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" -if [[ "${IMAGE_VERSION_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(?:-alpha\.[0-9]+)?$ ]]; then +if [[ "${IMAGE_VERSION_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-alpha\.[0-9]+)?$ ]]; then echo "🏷 Tagging and pushing image as '${DOCKER_IMAGE_TAG}:latest'" docker tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}" "${DOCKER_IMAGE_TAG}:latest" docker push "${DOCKER_IMAGE_TAG}:latest" From 3d4b3b69797374d962592420da3fa1e1ca00a65a Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Tue, 1 Nov 2022 11:17:03 +0000 Subject: [PATCH 04/10] Pin python requirements and bump to latest versions (#1140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Pin python requirements and bump to latest versions * fix(fileimports): add exception printing to file imports Co-authored-by: Gergő Jedlicska --- packages/fileimport-service/requirements.txt | 4 ++-- packages/fileimport-service/stl/import_file.py | 1 + packages/preview-service/Dockerfile | 2 +- utils/monitor-deployment/requirements.txt | 4 ++-- utils/test-deployment/requirements.txt | 4 ++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/fileimport-service/requirements.txt b/packages/fileimport-service/requirements.txt index 3cab894fa..7b3349251 100644 --- a/packages/fileimport-service/requirements.txt +++ b/packages/fileimport-service/requirements.txt @@ -1,2 +1,2 @@ -numpy-stl==2.16.3 -specklepy==2.5.1 +numpy-stl==2.17.1 +specklepy==2.9.0 diff --git a/packages/fileimport-service/stl/import_file.py b/packages/fileimport-service/stl/import_file.py index df667e510..8de5c4f95 100644 --- a/packages/fileimport-service/stl/import_file.py +++ b/packages/fileimport-service/stl/import_file.py @@ -66,6 +66,7 @@ if __name__ == '__main__': results = {'success': True, 'commitId': commit_id} except Exception as ex: results = {'success': False, 'error': str(ex)} + print(ex) with open(TMP_RESULTS_PATH, 'w') as f: json.dump(results, f) diff --git a/packages/preview-service/Dockerfile b/packages/preview-service/Dockerfile index aa83d9f00..538b71dac 100644 --- a/packages/preview-service/Dockerfile +++ b/packages/preview-service/Dockerfile @@ -28,7 +28,7 @@ COPY packages/viewer ./packages/viewer/ COPY packages/preview-service ./packages/preview-service/ # This way the foreach only builds the frontend and its deps -RUN yarn workspaces foreach -pt run build +RUN yarn workspaces foreach run build diff --git a/utils/monitor-deployment/requirements.txt b/utils/monitor-deployment/requirements.txt index d952f01b9..8d964f768 100644 --- a/utils/monitor-deployment/requirements.txt +++ b/utils/monitor-deployment/requirements.txt @@ -1,2 +1,2 @@ -psycopg2-binary -prometheus-client +psycopg2-binary==2.9.4 +prometheus-client==0.15.0 diff --git a/utils/test-deployment/requirements.txt b/utils/test-deployment/requirements.txt index 8a0414a42..c43e61aca 100644 --- a/utils/test-deployment/requirements.txt +++ b/utils/test-deployment/requirements.txt @@ -1,2 +1,2 @@ -specklepy -requests +specklepy==2.9.0 +requests==2.28.1 From 9abeb2645b9f283ae2ece5656bb8c95fb1d57414 Mon Sep 17 00:00:00 2001 From: Alexandru Popovici Date: Tue, 1 Nov 2022 16:30:36 +0200 Subject: [PATCH 05/10] New progressive rending pipeline. Progressive ambient occlusion (#1156) * Started working on progressive sao * Started working on the ao generation stage shader and pass * Continued with progressive SAO. Added the accumulation pass * Implemented progressive AO using the same estimator as for the dynamic one. * First draft of progressive AO with multiple selectable estimators. WIP on final implementation * Restructuring how the pipeline and it's passes work so we can extend/configure them more easily. Added the concept of SpecklePass * A lot of changes. Pipeline rework is more or less complete. Added individual output for some pipeline passes that we might want to inspect inside the sandbox. * Finished with displaying individual pipeline stages * Intruduced accumulation frames properly at pipeline level. Cleaned up progressive AO shader and pass. Exposed the number of accumulation frames as well as the kernel size in the sandbox. * Accumulation frame count is now reset after a resize * In the end I just implemented my own stationary/dynamic detection routine since the events sent from the 'camera controller' are dogshit * Implemented interpolating during frame accumulation between the dynamic SAO and the static AO. This makes the transition much more smooth and seamless than simply jumping straight to the static AO which begins accumulating from 0 * Added configurable intensity to static AO. Fixed an issue with static scenario detection where the accumulation frames were ran twice in succession, because the 'camera controller' is probably clamping the angles to X decimal points at the end of a camera motion cycle, which in turn triggers a camera move larger than our defined epsilon * Dynamic AO at half res * Some improvements to how we update the rendering pipeline and it's dynamic and progressive states * Trying to fix the issue with artifacts on progressive AO on mac. seems to be depth encoding related. Changed the depth pass to render to 32 float texture to check if the artifacts are gone * Trying out a new float->RGBA encoding and decoding * Added ner and far camera planes controls to see how they're values change depth encoding precision * Finally managed to get AO properly working with encoded linear depth * Trying 24 bit encoding * Fix for the macOS artifacts using linear depth buffer and a bias value * Extended the camera controller class and added/changed a few things so that the controller doesn't mess up our camera movement and also we now rely on the events sent by it to determine stationary vs dynamic camera scenarios. This way we spare the rendered from having to determine camera movement deltas by itslef(only to have it ruined by the camera controller anyway) * Added switching between perspective and linear depth when going from dynamic AO to progressive AO, so we don't have to integrate linear depth buffer sampling in the already existing features * Disabled normal texture rendering for dynamic AO * Some fixes and added dynamic AO back in at half res * Fixed an issue with converging progressive pipeline when using a trackpad * Implemented relative dynamic progressive AO kernel size * Fixed compile error * Fixed an issue with macOS and depth texture filtering * Added half res depth buffer switching for dynamic AO * Changed some params now that dynamic AO is at half res * Made section boxes work properly with our new progressive pipeline * Added correct kernel size computation for orthographic projection * Patched the camera controls to send events when zooming in orthographic mode * Some more integrations with camera views and fixed a bug with bad pipeline resets * Small fix for zoom and startup reset * Various fixes of issues I could find * Removed longs * preview-service now waits for the pipeline to convgerge before taking the screenshot * Handled #1110. Added the concept of 'ObjectLayers' which uses three's existing implementation for selective layer rendering/lighting/picking. Also added the BaseSpecklePass abstract class which provides a default implementation for any subclassing pass that needs to use specific layers when rendering. * Added more time between frames for the preview service. It seems to be generating the previews properly now Co-authored-by: Alex Co-authored-by: Alex --- .../preview-service/render_page/src/app.js | 4 +- packages/preview-service/routes/preview.js | 9 +- packages/viewer-sandbox/src/Sandbox.ts | 285 +++++++++---- packages/viewer-sandbox/src/main.ts | 9 +- packages/viewer/src/IViewer.ts | 5 +- packages/viewer/src/modules/DebugViewer.ts | 4 +- packages/viewer/src/modules/SectionBox.js | 18 +- .../viewer/src/modules/SpeckleRenderer.ts | 137 ++++-- packages/viewer/src/modules/Viewer.ts | 32 +- .../src/modules/context/CameraHanlder.js | 71 ++- .../src/modules/filtering/FilteringManager.ts | 2 +- .../src/modules/legacy/SelectionHelper.js | 2 + .../modules/materials/SpeckleDepthMaterial.ts | 18 + .../shaders/speckle-apply-ao-frag.ts | 20 + .../shaders/speckle-apply-ao-vert.ts | 6 + .../shaders/speckle-copy-output-frag.ts | 30 ++ .../shaders/speckle-copy-output-vert.ts | 6 + .../materials/shaders/speckle-depth-frag.ts | 21 +- .../materials/shaders/speckle-depth-vert.ts | 8 + .../materials/shaders/speckle-sao-frag.ts | 18 +- .../speckle-static-ao-accumulate-frag.ts | 11 + .../speckle-static-ao-accumulate-vert.ts | 6 + .../speckle-static-ao-generate-frag.ts | 296 +++++++++++++ .../speckle-static-ao-generate-vert.ts | 6 + .../modules/objects/SpeckleCameraControls.ts | 339 +++++++++++++++ .../src/modules/objects/SpeckleRaycaster.ts | 2 + .../src/modules/pipeline/ApplyAOPass.ts | 109 +++++ .../src/modules/pipeline/ApplySAOPass.ts | 48 --- .../viewer/src/modules/pipeline/ColorPass.ts | 74 ++++ .../src/modules/pipeline/CopyOutputPass.ts | 56 +++ .../viewer/src/modules/pipeline/DepthPass.ts | 153 +++++++ .../src/modules/pipeline/DynamicAOPass.ts | 312 ++++++++++++++ .../src/modules/pipeline/NormalsPass.ts | 95 +++++ .../viewer/src/modules/pipeline/Pipeline.ts | 403 +++++++++++++++--- .../src/modules/pipeline/SpecklePass.ts | 58 +++ .../src/modules/pipeline/SpeckleSAOPass.ts | 306 ------------- .../src/modules/pipeline/StaticAOPass.ts | 304 +++++++++++++ 37 files changed, 2712 insertions(+), 571 deletions(-) create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-apply-ao-frag.ts create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-apply-ao-vert.ts create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-copy-output-frag.ts create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-copy-output-vert.ts create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-static-ao-accumulate-frag.ts create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-static-ao-accumulate-vert.ts create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-static-ao-generate-frag.ts create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-static-ao-generate-vert.ts create mode 100644 packages/viewer/src/modules/objects/SpeckleCameraControls.ts create mode 100644 packages/viewer/src/modules/pipeline/ApplyAOPass.ts delete mode 100644 packages/viewer/src/modules/pipeline/ApplySAOPass.ts create mode 100644 packages/viewer/src/modules/pipeline/ColorPass.ts create mode 100644 packages/viewer/src/modules/pipeline/CopyOutputPass.ts create mode 100644 packages/viewer/src/modules/pipeline/DepthPass.ts create mode 100644 packages/viewer/src/modules/pipeline/DynamicAOPass.ts create mode 100644 packages/viewer/src/modules/pipeline/NormalsPass.ts create mode 100644 packages/viewer/src/modules/pipeline/SpecklePass.ts delete mode 100644 packages/viewer/src/modules/pipeline/SpeckleSAOPass.ts create mode 100644 packages/viewer/src/modules/pipeline/StaticAOPass.ts diff --git a/packages/preview-service/render_page/src/app.js b/packages/preview-service/render_page/src/app.js index 554b567e7..ad52ee7d5 100644 --- a/packages/preview-service/render_page/src/app.js +++ b/packages/preview-service/render_page/src/app.js @@ -1,6 +1,6 @@ -import { Viewer, DefaultViewerParams } from '@speckle/viewer' +import { DebugViewer, DefaultViewerParams } from '@speckle/viewer' -const v = new Viewer(document.getElementById('renderer'), DefaultViewerParams) +const v = new DebugViewer(document.getElementById('renderer'), DefaultViewerParams) window.v = v // v.on( ViewerEvent.LoadProgress, args => console.log( args ) ) diff --git a/packages/preview-service/routes/preview.js b/packages/preview-service/routes/preview.js index 75384a6e7..c55b9ffc7 100644 --- a/packages/preview-service/routes/preview.js +++ b/packages/preview-service/routes/preview.js @@ -29,10 +29,15 @@ async function pageFunction(objectUrl) { window.v.zoom(undefined, 0.95, false) await waitForAnimation(100) - // full 360 for (let i = 0; i < 24; i++) { window.v.setView({ azimuth: Math.PI / 12, polar: 0 }, false) - await waitForAnimation() + window.v.getRenderer().resetPipeline(true) + /** Not sure what the frame time when running pupeteer is, but it's not 16ms. + * That's why we're allowing more time between frames than probably needed + * In a future update, we'll have the viewer signal when convergence is complete + * regradless of how many frames/time that takes + */ + await waitForAnimation(2500) ret.scr[i + ''] = await window.v.screenshot() } diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index 4d84de649..07d0fcbff 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -30,21 +30,30 @@ export default class Sandbox { tonemapping: 4 //'ACESFilmicToneMapping' } - public static postParams = { - saoEnabled: true, - saoParams: { - saoBias: 0.15, - saoIntensity: 1.25, - saoScale: 434, - saoKernelRadius: 10, - saoMinResolution: 0, - saoBlur: true, - saoBlurRadius: 4, - saoBlurStdDev: 4, - saoBlurDepthCutoff: 0.0007 + public static pipelineParams = { + pipelineOutput: 8, + accumulationFrames: 16, + dynamicAoEnabled: true, + dynamicAoParams: { + intensity: 1.5, + scale: 0, + kernelRadius: 5, + bias: 0.2, + normalsType: 2, + blurEnabled: true, + blurRadius: 2, + blurStdDev: 4, + blurDepthCutoff: 0.007 }, - saoScaleOffset: 0, - saoNormalsRendering: 2 + staticAoEnabled: true, + staticAoParams: { + intensity: 1, + kernelRadius: 30, // Screen space + kernelSize: 16, + bias: 0.01, + minDistance: 0, + maxDistance: 0.008 + } } public static lightParams: SunLightConfiguration = { @@ -52,8 +61,8 @@ export default class Sandbox { castShadow: true, intensity: 5, color: 0xffffff, - elevation: 1.33, azimuth: 0.75, + elevation: 1.33, radius: 0, indirectLightIntensity: 1.2 } @@ -256,6 +265,7 @@ export default class Sandbox { }) for (let i = 0; i < 24; i++) { this.viewer.setView({ azimuth: Math.PI / 12, polar: 0 }, false) + this.viewer.getRenderer().resetPipeline(true) await waitForAnimation(1000) } }) @@ -327,6 +337,30 @@ export default class Sandbox { this.viewer.requestRender() }) + postFolder + .addInput({ near: 0.01 }, 'near', { + min: 0, + max: 2, + step: 0.001 + }) + .on('change', (ev) => { + this.viewer.cameraHandler.activeCam.camera.near = ev.value + this.viewer.cameraHandler.activeCam.camera.updateProjectionMatrix() + this.viewer.requestRender() + }) + + postFolder + .addInput({ far: 10 }, 'far', { + min: 0, + max: 10000, + step: 1 + }) + .on('change', (ev) => { + this.viewer.cameraHandler.activeCam.camera.far = ev.value + this.viewer.cameraHandler.activeCam.camera.updateProjectionMatrix() + this.viewer.requestRender() + }) + postFolder .addInput(Sandbox.sceneParams, 'tonemapping', { options: { @@ -338,52 +372,74 @@ export default class Sandbox { this.viewer.getRenderer().renderer.toneMapping = Sandbox.sceneParams.tonemapping this.viewer.requestRender() }) - postFolder - .addInput(Sandbox.postParams, 'saoEnabled', { label: 'SAO-ENABLED' }) + + const pipelineFolder = this.tabs.pages[1].addFolder({ + title: 'Pipeline', + expanded: true + }) + pipelineFolder + .addInput(Sandbox.pipelineParams, 'pipelineOutput', { + options: { + DEPTH_RGBA: 0, + DEPTH: 1, + COLOR: 2, + GEOMETRY_NORMALS: 3, + RECONSTRUCTED_NORMALS: 4, + DYNAMIC_AO: 5, + DYNAMIC_AO_BLURED: 6, + PROGRESSIVE_AO: 7, + FINAL: 8 + } + }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams this.viewer.requestRender() }) - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoBias', { + + pipelineFolder + .addInput(Sandbox.pipelineParams, 'accumulationFrames', { + min: 1, + max: 128, + step: 1 + }) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) + + const dynamicAoFolder = pipelineFolder.addFolder({ + title: 'Dynamic AO', + expanded: true + }) + + dynamicAoFolder + .addInput(Sandbox.pipelineParams.dynamicAoParams, 'intensity', { min: 0, max: 5 }) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) + + dynamicAoFolder + .addInput(Sandbox.pipelineParams.dynamicAoParams, 'kernelRadius', { + min: 0, + max: 500 + }) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) + + dynamicAoFolder + .addInput(Sandbox.pipelineParams.dynamicAoParams, 'bias', { min: -1, max: 1 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams this.viewer.requestRender() }) - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoIntensity', { - min: 0, - max: 5 - }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - - // postFolder - // .addInput(Sandbox.postParams.saoParams, 'saoScale', { - // min: 0, - // max: 100 - // }) - // .on('change', () => { - // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - // this.viewer.requestRender() - // }) - postFolder - .addInput(Sandbox.postParams, 'saoScaleOffset', { - min: -100, - max: 100 - }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - - postFolder - .addInput(Sandbox.postParams, 'saoNormalsRendering', { + dynamicAoFolder + .addInput(Sandbox.pipelineParams.dynamicAoParams, 'normalsType', { options: { DEFAULT: 0, ADVANCED: 1, @@ -391,63 +447,110 @@ export default class Sandbox { } }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams this.viewer.requestRender() }) - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoKernelRadius', { + dynamicAoFolder + .addInput(Sandbox.pipelineParams.dynamicAoParams, 'blurEnabled', {}) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) + + dynamicAoFolder + .addInput(Sandbox.pipelineParams.dynamicAoParams, 'blurRadius', { min: 0, - max: 100 + max: 10 }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams this.viewer.requestRender() }) - // postFolder - // .addInput(Sandbox.postParams.saoParams, 'saoMinResolution', { - // min: 0, - // max: 1 - // }) - // .on('change', () => { - // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - // this.viewer.requestRender() - // }) - - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoBlur', {}) + dynamicAoFolder + .addInput(Sandbox.pipelineParams.dynamicAoParams, 'blurDepthCutoff', { + min: 0, + max: 1, + step: 0.00001 + }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams this.viewer.requestRender() }) - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoBlurRadius', { min: 0, max: 10 }) + const staticAoFolder = pipelineFolder.addFolder({ + title: 'Static AO', + expanded: true + }) + // staticAoFolder + // .addInput(Sandbox.pipelineParams, 'staticAoEnabled', {}) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + // this.viewer.requestRender() + // }) + staticAoFolder + .addInput(Sandbox.pipelineParams.staticAoParams, 'intensity', { + min: 0, + max: 5, + step: 0.01 + }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) + staticAoFolder + .addInput(Sandbox.pipelineParams.staticAoParams, 'minDistance', { + min: 0, + max: 100, + step: 0.000001 + }) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams this.viewer.requestRender() }) - // postFolder - // .addInput(Sandbox.postParams.saoParams, 'saoBlurStdDev', { - // min: 0, - // max: 150 - // }) - // .on('change', () => { - // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - // this.viewer.requestRender() - // }) + staticAoFolder + .addInput(Sandbox.pipelineParams.staticAoParams, 'maxDistance', { + min: 0, + max: 100, + step: 0.000001 + }) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) + staticAoFolder + .addInput(Sandbox.pipelineParams.staticAoParams, 'kernelRadius', { + min: 0, + max: 1000 + }) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) - // postFolder - // .addInput(Sandbox.postParams.saoParams, 'saoBlurDepthCutoff', { - // min: 0, - // max: 10 - // }) - // .on('change', () => { - // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - // this.viewer.requestRender() - // }) + staticAoFolder + .addInput(Sandbox.pipelineParams.staticAoParams, 'bias', { + min: -1, + max: 1, + step: 0.0001 + }) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) + + staticAoFolder + .addInput(Sandbox.pipelineParams.staticAoParams, 'kernelSize', { + min: 1, + max: 128, + step: 1 + }) + .on('change', () => { + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams + this.viewer.requestRender() + }) const lightsFolder = this.tabs.pages[1].addFolder({ title: 'Lights', diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 95a054a3b..349a1cb3a 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -85,9 +85,6 @@ await sandbox.loadUrl( 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' // 'Super' heavy revit shit // 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' - // Same sample revit house, local to dim's computer - // 'http://localhost:3000/streams/6960695d7b/commits/da0a2343fa' - // 'http://100.66.180.109:3000/streams/6960695d7b/commits/417526751d' // IFC building (good for a tree based structure) // 'https://latest.speckle.dev/streams/92b620fb17/commits/2ebd336223' // IFC story, a subtree of the above @@ -120,9 +117,13 @@ await sandbox.loadUrl( // REVIT test stream // 'https://latest.speckle.dev/streams/c544db35f5/commits/7c29374369' // Arcs - //'https://latest.speckle.dev/streams/0c6ad366c4/commits/912d83412e' + // 'https://latest.speckle.dev/streams/0c6ad366c4/commits/912d83412e' // Freezers // 'https://speckle.xyz/streams/f0532359ac/commits/98678e2a3d?c=%5B2455.15367,2689.87156,4366.68444,205.422,-149.41199,148.749,0,1%5D' + //Gergo's house + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/78bdd8eb76' // Point cloud // 'https://latest.speckle.dev/streams/2d19273d31/commits/9ceb423feb' + // Luis sphere + // 'https://speckle.xyz/streams/b85d53c3b4/commits/b47f21b707' ) diff --git a/packages/viewer/src/IViewer.ts b/packages/viewer/src/IViewer.ts index 07ca0b93e..37ee844ec 100644 --- a/packages/viewer/src/IViewer.ts +++ b/packages/viewer/src/IViewer.ts @@ -46,7 +46,8 @@ export enum ViewerEvent { UnloadComplete = 'unload-complete', UnloadAllComplete = 'unload-all-complete', Busy = 'busy', - SectionBoxChanged = 'section-box-changed' + SectionBoxChanged = 'section-box-changed', + SectionBoxUpdated = 'section-box-updated' } export type SelectionEvent = { @@ -117,7 +118,7 @@ export interface IViewer { init(): Promise resize(): void on(eventType: ViewerEvent, handler: (arg) => void) - + requestRender(): void setSectionBox( box?: { min: { x: number; y: number; z: number } diff --git a/packages/viewer/src/modules/DebugViewer.ts b/packages/viewer/src/modules/DebugViewer.ts index d449d3f91..e3a66c231 100644 --- a/packages/viewer/src/modules/DebugViewer.ts +++ b/packages/viewer/src/modules/DebugViewer.ts @@ -4,9 +4,7 @@ export class DebugViewer extends Viewer { getRenderer() { return this.speckleRenderer } - requestRender() { - this.needsRender = true - } + requestRenderShadowmap() { this.getRenderer().updateDirectLights() } diff --git a/packages/viewer/src/modules/SectionBox.js b/packages/viewer/src/modules/SectionBox.js index f77eef0c9..e785005c3 100644 --- a/packages/viewer/src/modules/SectionBox.js +++ b/packages/viewer/src/modules/SectionBox.js @@ -3,6 +3,7 @@ import SelectionHelper from './legacy/SelectionHelper' import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js' import { Box3 } from 'three' import { ViewerEvent } from '../IViewer' +import { ObjectLayers } from './SpeckleRenderer' export default class SectionBox { constructor(viewer) { @@ -13,6 +14,7 @@ export default class SectionBox { this.dragging = false this.display = new THREE.Group() this.display.name = 'SectionBox' + this.display.layers.set(ObjectLayers.PROPS) this.viewer.speckleRenderer.scene.add(this.display) // box @@ -25,11 +27,13 @@ export default class SectionBox { }) this.cube = new THREE.Mesh(this.boxGeometry, this.material) this.cube.visible = false + this.cube.layers.set(ObjectLayers.PROPS) this.display.add(this.cube) this.boxHelper = new THREE.BoxHelper(this.cube, 0x0a66ff) this.boxHelper.material.opacity = 0.4 + this.boxHelper.layers.set(ObjectLayers.PROPS) this.display.add(this.boxHelper) // we're attaching the gizmo mover to this sphere in the box centre @@ -38,6 +42,7 @@ export default class SectionBox { sphere, new THREE.MeshStandardMaterial({ color: 0x00ffff }) ) + this.sphere.layers.set(ObjectLayers.PROPS) this.sphere.visible = false this.display.add(this.sphere) @@ -56,6 +61,7 @@ export default class SectionBox { }) ) this.hoverPlane.visible = false + this.hoverPlane.layers.set(ObjectLayers.PROPS) this.display.add(this.hoverPlane) this.dragging = false @@ -125,6 +131,12 @@ export default class SectionBox { this.viewer.cameraHandler.activeCam.camera, this.viewer.speckleRenderer.renderer.domElement ) + for (let k = 0; k < this.controls.children.length; k++) { + this.controls.children[k].traverse((obj) => { + obj.layers.set(ObjectLayers.PROPS) + }) + } + this.controls.getRaycaster().layers.set(ObjectLayers.PROPS) this.controls.setSize(0.75) this.display.add(this.controls) this.controls.addEventListener('change', this._draggingChangeHandler.bind(this)) @@ -153,10 +165,11 @@ export default class SectionBox { _draggingChangeHandler() { if (!this.display.visible) return this.boxHelper.update() - this._generateOrUpdatePlanes() + // this._generateOrUpdatePlanes() // Dragging a side / plane if (this.dragging && this.currentRange) { + this._generateOrUpdatePlanes() if (this.prevPosition === null) this.prevPosition = this.hoverPlane.position.clone() this.prevPosition.sub(this.hoverPlane.position) @@ -178,6 +191,7 @@ export default class SectionBox { // Dragging the whole section box if (this.dragging && !this.currentRange) { + this._generateOrUpdatePlanes() if (this.prevPosition === null) this.prevPosition = this.sphere.position.clone() this.prevPosition.sub(this.sphere.position) this.prevPosition.negate() @@ -196,6 +210,7 @@ export default class SectionBox { } this.viewer.needsRender = true this.viewer.emit('section-box-changed', this.getCurrentBox()) + this.viewer.requestRender() } _clickHandler(args) { @@ -327,6 +342,7 @@ export default class SectionBox { plane.setFromCoplanarPoints(a, b, c) index++ } + this.viewer.emit('section-box-updated', this.getCurrentBox()) } _attachControlsToBox() { diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index 11b49ddde..943ee74b4 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -2,7 +2,6 @@ import { ACESFilmicToneMapping, Box3, Box3Helper, - Camera, CameraHelper, Color, DirectionalLight, @@ -45,12 +44,26 @@ import { SunLightConfiguration, ViewerEvent } from '../IViewer' -import { DefaultPipelineOptions, Pipeline, PipelineOptions } from './pipeline/Pipeline' +import { + DefaultPipelineOptions, + Pipeline, + PipelineOptions, + RenderType +} from './pipeline/Pipeline' + +export enum ObjectLayers { + STREAM_CONTENT = 1, + PROPS = 2 +} export default class SpeckleRenderer { private readonly SHOW_HELPERS = false + private readonly ANGLE_EPSILON = 0.0001 + private readonly POSITION_REST_EPSILON = 0.001 + private readonly POSITION_RESUME_EPSILON = 0.001 private _renderer: WebGLRenderer - public scene: Scene + public _scene: Scene + private _needsRender: boolean private rootGroup: Group private batcher: Batcher private intersections: Intersections @@ -61,13 +74,21 @@ export default class SpeckleRenderer { public viewer: Viewer // TEMPORARY private filterBatchRecording: string[] private pipeline: Pipeline + private lastAzimuth: number + private lastPolar: number + private lastCameraPosition: Vector3 = new Vector3() + private lastCameraMotionDelta: number public get renderer(): WebGLRenderer { return this._renderer } + public set needsRender(value: boolean) { + this._needsRender ||= value + } + public set indirectIBL(texture: Texture) { - this.scene.environment = texture + this._scene.environment = texture } public set indirectIBLIntensity(value: number) { @@ -85,11 +106,11 @@ export default class SpeckleRenderer { /** TEMPORARY for backwards compatibility */ public get allObjects() { - return this.scene.getObjectByName('ContentGroup') + return this._scene.getObjectByName('ContentGroup') } public subtree(subtreeId: string) { - return this.scene.getObjectByName(subtreeId) + return this._scene.getObjectByName(subtreeId) } public get sceneBox() { @@ -108,15 +129,24 @@ export default class SpeckleRenderer { return this.sun } + public get camera() { + return this.viewer.cameraHandler.activeCam.camera + } + + public get scene() { + return this._scene + } + public set pipelineOptions(value: PipelineOptions) { this.pipeline.pipelineOptions = value } public constructor(viewer: Viewer /** TEMPORARY */) { - this.scene = new Scene() + this._scene = new Scene() this.rootGroup = new Group() this.rootGroup.name = 'ContentGroup' - this.scene.add(this.rootGroup) + this.rootGroup.layers.set(ObjectLayers.STREAM_CONTENT) + this._scene.add(this.rootGroup) this.batcher = new Batcher() this.intersections = new Intersections() @@ -144,7 +174,7 @@ export default class SpeckleRenderer { container.appendChild(this._renderer.domElement) this.pipeline = new Pipeline(this._renderer, this.batcher) - this.pipeline.configure(this.scene, this.viewer.cameraHandler.activeCam.camera) + this.pipeline.configure() this.pipeline.pipelineOptions = DefaultPipelineOptions this.input = new Input(this._renderer.domElement, InputOptionsDefault) @@ -156,24 +186,60 @@ export default class SpeckleRenderer { if (this.SHOW_HELPERS) { const helpers = new Group() helpers.name = 'Helpers' - this.scene.add(helpers) + this._scene.add(helpers) const sceneBoxHelper = new Box3Helper(this.sceneBox, new Color(0x0000ff)) sceneBoxHelper.name = 'SceneBoxHelper' + sceneBoxHelper.layers.set(ObjectLayers.PROPS) helpers.add(sceneBoxHelper) const dirLightHelper = new DirectionalLightHelper(this.sun, 50, 0xff0000) dirLightHelper.name = 'DirLightHelper' + dirLightHelper.layers.set(ObjectLayers.PROPS) helpers.add(dirLightHelper) const camHelper = new CameraHelper(this.sun.shadow.camera) camHelper.name = 'CamHelper' + camHelper.layers.set(ObjectLayers.PROPS) helpers.add(camHelper) } + + this.viewer.cameraHandler.controls.restThreshold = 0.001 + this.viewer.cameraHandler.controls.addEventListener('rest', () => { + this._needsRender = true + this.pipeline.onStationaryBegin() + }) + this.viewer.cameraHandler.controls.addEventListener('controlstart', () => { + this._needsRender = true + this.pipeline.onStationaryEnd() + }) + + this.viewer.cameraHandler.controls.addEventListener('controlend', () => { + this._needsRender = true + if (this.viewer.cameraHandler.controls.hasRested) + this.pipeline.onStationaryBegin() + }) + + this.viewer.cameraHandler.controls.addEventListener('control', () => { + this._needsRender = true + this.pipeline.onStationaryEnd() + }) + this.viewer.cameraHandler.controls.addEventListener('update', () => { + if ( + !this.viewer.cameraHandler.controls.hasRested && + this.pipeline.renderType === RenderType.ACCUMULATION + ) { + this._needsRender = true + this.pipeline.onStationaryEnd() + } + }) } public update(deltaTime: number) { + this.needsRender = this.viewer.cameraHandler.controls.update(deltaTime) + this.batcher.update(deltaTime) + const viewer = new Vector3() const viewerLow = new Vector3() const viewerHigh = new Vector3() @@ -269,19 +335,27 @@ export default class SpeckleRenderer { this.viewer.cameraHandler.activeCam.camera.far = d this.viewer.cameraHandler.activeCam.camera.updateProjectionMatrix() this.viewer.cameraHandler.camera.updateProjectionMatrix() - this.pipeline.pipelineOptions = { saoParams: { saoScale: d } } - // console.log(d) + + this.pipeline.update(this) } - public render(camera: Camera) { - this.batcher.render(this.renderer) - this.pipeline.render(this.scene, camera) - // this.renderer.render(this.scene, camera) + public resetPipeline(force = false) { + this._needsRender = true + if (this.viewer.cameraHandler.controls.hasRested || force) this.pipeline.reset() + } + + public render(): void { + if (this._needsRender) { + this.batcher.render(this.renderer) + this._needsRender = this.pipeline.render() + // this.renderer.render(this.scene, camera) + } } public resize(width: number, height: number) { this.renderer.setSize(width, height) this.pipeline.resize(width, height) + this._needsRender = true } public addRenderTree(subtreeId: string) { @@ -311,11 +385,13 @@ export default class SpeckleRenderer { const subtreeGroup = new Group() subtreeGroup.name = subtreeId + subtreeGroup.layers.set(ObjectLayers.STREAM_CONTENT) this.rootGroup.add(subtreeGroup) const batches = this.batcher.getBatches(subtreeId) batches.forEach((batch: Batch) => { const batchRenderable = batch.renderObject + batchRenderable.layers.set(ObjectLayers.STREAM_CONTENT) subtreeGroup.add(batch.renderObject) if (batch.geometryType === GeometryType.MESH) { const mesh = batchRenderable as unknown as Mesh @@ -333,6 +409,8 @@ export default class SpeckleRenderer { this.updateDirectLights() this.updateHelpers() + // this.resetPipeline(true) + this._needsRender = true } public removeRenderTree(subtreeId: string) { @@ -359,6 +437,7 @@ export default class SpeckleRenderer { public endFilter() { this.batcher.autoFillDrawRanges(this.filterBatchRecording) + this.updateClippingPlanes(this.viewer.sectionBox.planes) this.renderer.shadowMap.needsUpdate = true } @@ -378,12 +457,14 @@ export default class SpeckleRenderer { }) this.pipeline.updateClippingPlanes(planes) this.renderer.shadowMap.needsUpdate = true + this.resetPipeline() } private addDirectLights() { this.sun = new DirectionalLight(0xffffff, 5) this.sun.name = 'sun' - this.scene.add(this.sun) + this.sun.layers.set(ObjectLayers.STREAM_CONTENT) + this._scene.add(this.sun) this.sun.castShadow = true @@ -402,7 +483,7 @@ export default class SpeckleRenderer { this.sun.shadow.radius = 2 this.sunTarget = new Object3D() - this.scene.add(this.sunTarget) + this._scene.add(this.sunTarget) this.sunTarget.position.copy(this.sceneCenter) this.sun.target = this.sunTarget } @@ -467,8 +548,9 @@ export default class SpeckleRenderer { this.sun.shadow.camera.far = Math.abs(lightSpaceBox.min.z) this.sun.shadow.camera.updateProjectionMatrix() this.renderer.shadowMap.needsUpdate = true - this.viewer.needsRender = true + this.needsRender = true this.updateHelpers() + this.resetPipeline() } public setSunLightConfiguration(config: SunLightConfiguration) { @@ -481,12 +563,14 @@ export default class SpeckleRenderer { public updateHelpers() { if (this.SHOW_HELPERS) { - ;(this.scene.getObjectByName('CamHelper') as CameraHelper).update() + ;(this._scene.getObjectByName('CamHelper') as CameraHelper).update() // Thank you prettier, this looks so much better - ;(this.scene.getObjectByName('SceneBoxHelper') as Box3Helper).box.copy( + ;(this._scene.getObjectByName('SceneBoxHelper') as Box3Helper).box.copy( this.sceneBox ) - ;(this.scene.getObjectByName('DirLightHelper') as DirectionalLightHelper).update() + ;( + this._scene.getObjectByName('DirLightHelper') as DirectionalLightHelper + ).update() } } @@ -527,7 +611,7 @@ export default class SpeckleRenderer { private onObjectClick(e) { const results: Array = this.intersections.intersect( - this.scene, + this._scene, this.viewer.cameraHandler.activeCam.camera, e, true, @@ -566,7 +650,7 @@ export default class SpeckleRenderer { private onObjectDoubleClick(e) { const results: Array = this.intersections.intersect( - this.scene, + this._scene, this.viewer.cameraHandler.activeCam.camera, e, true, @@ -627,9 +711,11 @@ export default class SpeckleRenderer { public zoom(objectIds?: string[], fit?: number, transition?: boolean) { if (!objectIds) { this.zoomExtents(fit, transition) + this.pipeline.onStationaryEnd() return } this.zoomToBox(this.boxFromObjects(objectIds), fit, transition) + this.pipeline.onStationaryEnd() } /** Taken from InteractionsHandler. Will revisit in the future */ @@ -745,6 +831,7 @@ export default class SpeckleRenderer { if (this.isPolarView(view)) { this.setViewPolar(view, transition) } + this.pipeline.onStationaryEnd() } private setViewSpeckle(view: SpeckleView, transition = true) { @@ -851,7 +938,7 @@ export default class SpeckleRenderer { /** DEBUG */ public onObjectClickDebug(e) { const results: Array = this.intersections.intersect( - this.scene, + this._scene, this.viewer.cameraHandler.activeCam.camera, e, true, diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index 9fbbd82a2..3fbf27fd3 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -45,22 +45,11 @@ export class Viewer extends EventEmitter implements IViewer { public sectionBox: SectionBox public cameraHandler: CameraHandler - /** Render flag for on-demand rendering */ - private _needsRender: boolean - /** Misc members */ private inProgressOperations: number private clock: Clock private loaders: { [id: string]: ViewerObjectLoader } = {} - public get needsRender(): boolean { - return this._needsRender - } - - public set needsRender(value: boolean) { - this._needsRender = value || this._needsRender - } - /** Gets the World object. Currently it's used for info mostly */ public static get World(): World { return this.world @@ -96,13 +85,12 @@ export class Viewer extends EventEmitter implements IViewer { this.sectionBox = new SectionBox(this) this.sectionBox.off() - this.sectionBox.controls.addEventListener('change', () => { + this.on(ViewerEvent.SectionBoxUpdated, () => { this.speckleRenderer.updateClippingPlanes(this.sectionBox.planes) }) this.frame() this.resize() - this.needsRender = true this.on(ViewerEvent.LoadComplete, (url) => { WorldTree.getRenderTree(url).buildRenderTree() @@ -135,8 +123,14 @@ export class Viewer extends EventEmitter implements IViewer { } public resize() { - this.speckleRenderer.resize(this.container.offsetWidth, this.container.offsetHeight) - this.needsRender = true + const width = this.container.offsetWidth + const height = this.container.offsetHeight + this.speckleRenderer.resize(width, height) + } + + public requestRender() { + this.speckleRenderer.needsRender = true + this.speckleRenderer.resetPipeline() } private frame() { @@ -146,17 +140,13 @@ export class Viewer extends EventEmitter implements IViewer { private update() { const delta = this.clock.getDelta() - this.needsRender = this.cameraHandler.controls.update(delta) this.speckleRenderer.update(delta) this.stats?.update() requestAnimationFrame(this.frame.bind(this)) } private render() { - if (this.needsRender) { - this.speckleRenderer.render(this.cameraHandler.activeCam.camera) - // this._needsRender = false - } + this.speckleRenderer.render() } public async init(): Promise { @@ -320,6 +310,7 @@ export class Viewer extends EventEmitter implements IViewer { public toggleCameraProjection() { this.cameraHandler.toggleCameras() + this.speckleRenderer.resetPipeline() } public setLightConfiguration(config: SunLightConfiguration): void { @@ -345,7 +336,6 @@ export class Viewer extends EventEmitter implements IViewer { transition = true ): void { this.speckleRenderer.setView(view, transition) - this.needsRender = true } public screenshot(): Promise { diff --git a/packages/viewer/src/modules/context/CameraHanlder.js b/packages/viewer/src/modules/context/CameraHanlder.js index 40729c57f..e1b7b935b 100644 --- a/packages/viewer/src/modules/context/CameraHanlder.js +++ b/packages/viewer/src/modules/context/CameraHanlder.js @@ -1,6 +1,7 @@ import * as THREE from 'three' import CameraControls from 'camera-controls' -import { KeyboardKeyHold } from 'hold-event' +import { HOLD_EVENT_TYPE, KeyboardKeyHold } from 'hold-event' +import { SpeckleCameraControls } from '../objects/SpeckleCameraControls' export default class CameraHandler { constructor(viewer) { @@ -30,7 +31,8 @@ export default class CameraHandler { this.orthoCamera.updateProjectionMatrix() CameraControls.install({ THREE }) - this.controls = new CameraControls(this.camera, this.viewer.container) + SpeckleCameraControls.install() + this.controls = new SpeckleCameraControls(this.camera, this.viewer.container) this.controls.maxPolarAngle = Math.PI / 2 this.setupWASDControls() @@ -185,38 +187,103 @@ export default class CameraHandler { const aKey = new KeyboardKeyHold(KEYCODE.A, 16.666) const sKey = new KeyboardKeyHold(KEYCODE.S, 16.666) const dKey = new KeyboardKeyHold(KEYCODE.D, 16.666) + const isTruckingGroup = new Array(4) + + const setTrucking = (index, value) => { + isTruckingGroup[index] = value + if (isTruckingGroup.every((element) => element === false)) { + this.controls.isTrucking = false + this.controls.dispatchEvent({ type: 'rest' }) + } else this.controls.isTrucking = true + } + + aKey.addEventListener( + HOLD_EVENT_TYPE.HOLD_START, + function () { + this.controls.dispatchEvent({ type: 'controlstart' }) + }.bind(this) + ) aKey.addEventListener( 'holding', function (event) { if (this.viewer.mouseOverRenderer === false) return + setTrucking(0, true) this.controls.truck(-0.01 * event.deltaTime, 0, false) return }.bind(this) ) + aKey.addEventListener( + HOLD_EVENT_TYPE.HOLD_END, + function () { + setTrucking(0, false) + }.bind(this) + ) + + dKey.addEventListener( + HOLD_EVENT_TYPE.HOLD_START, + function () { + this.controls.dispatchEvent({ type: 'controlstart' }) + }.bind(this) + ) dKey.addEventListener( 'holding', function (event) { if (this.viewer.mouseOverRenderer === false) return + setTrucking(1, true) this.controls.truck(0.01 * event.deltaTime, 0, false) return }.bind(this) ) + dKey.addEventListener( + HOLD_EVENT_TYPE.HOLD_END, + function () { + setTrucking(1, false) + }.bind(this) + ) + + wKey.addEventListener( + HOLD_EVENT_TYPE.HOLD_START, + function () { + this.controls.dispatchEvent({ type: 'controlstart' }) + }.bind(this) + ) wKey.addEventListener( 'holding', function (event) { if (this.viewer.mouseOverRenderer === false) return + setTrucking(2, true) this.controls.forward(0.01 * event.deltaTime, false) return }.bind(this) ) + wKey.addEventListener( + HOLD_EVENT_TYPE.HOLD_END, + function () { + setTrucking(2, false) + }.bind(this) + ) + + sKey.addEventListener( + HOLD_EVENT_TYPE.HOLD_START, + function () { + this.controls.dispatchEvent({ type: 'controlstart' }) + }.bind(this) + ) sKey.addEventListener( 'holding', function (event) { if (this.viewer.mouseOverRenderer === false) return + setTrucking(3, true) this.controls.forward(-0.01 * event.deltaTime, false) return }.bind(this) ) + sKey.addEventListener( + HOLD_EVENT_TYPE.HOLD_END, + function () { + setTrucking(3, false) + }.bind(this) + ) } onWindowResize() { diff --git a/packages/viewer/src/modules/filtering/FilteringManager.ts b/packages/viewer/src/modules/filtering/FilteringManager.ts index 1e85753b9..a0e927b64 100644 --- a/packages/viewer/src/modules/filtering/FilteringManager.ts +++ b/packages/viewer/src/modules/filtering/FilteringManager.ts @@ -451,7 +451,7 @@ export class FilteringManager { } this.Renderer.endFilter() - this.Renderer.viewer.needsRender = true + this.Renderer.viewer.requestRender() return returnState } diff --git a/packages/viewer/src/modules/legacy/SelectionHelper.js b/packages/viewer/src/modules/legacy/SelectionHelper.js index da704fd02..35d87902e 100644 --- a/packages/viewer/src/modules/legacy/SelectionHelper.js +++ b/packages/viewer/src/modules/legacy/SelectionHelper.js @@ -1,5 +1,6 @@ import * as THREE from 'three' import EventEmitter from '../EventEmitter' +import { ObjectLayers } from '../SpeckleRenderer' /** * Selects and deselects user added objects in the scene. Emits the array of all intersected objects on click. @@ -19,6 +20,7 @@ export default class _SelectionHelper extends EventEmitter { this.raycaster.params.Line.threshold = 0.1 this.raycaster.params.Line2 = {} this.raycaster.params.Line2.threshold = 1 + this.raycaster.layers.set(ObjectLayers.PROPS) // optional param allows for raycasting against a subset of objects // this.subset = typeof _options !== 'undefined' && typeof _options.subset !== 'undefined' ? _options.subset : null; diff --git a/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts b/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts index 3a905f119..3643318b9 100644 --- a/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts +++ b/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts @@ -25,6 +25,8 @@ class SpeckleDepthMaterial extends MeshDepthMaterial { this.userData.rteModelViewMatrix = { value: new Matrix4() } + this.userData.near = { value: 0 } + this.userData.far = { value: 0 } ;(this as any).vertProgram = speckleDepthVert ;(this as any).fragProgram = speckleDepthFrag ;(this as any).uniforms = UniformsUtils.merge([ @@ -38,6 +40,12 @@ class SpeckleDepthMaterial extends MeshDepthMaterial { }, rteModelViewMatrix: { value: this.userData.rteModelViewMatrix.value + }, + near: { + value: this.userData.near.value + }, + far: { + value: this.userData.far.value } } ]) @@ -46,6 +54,8 @@ class SpeckleDepthMaterial extends MeshDepthMaterial { shader.uniforms.uViewer_high = this.userData.uViewer_high shader.uniforms.uViewer_low = this.userData.uViewer_low shader.uniforms.rteModelViewMatrix = this.userData.rteModelViewMatrix + shader.uniforms.near = this.userData.near + shader.uniforms.far = this.userData.far shader.vertexShader = this.vertProgram shader.fragmentShader = this.fragProgram } @@ -69,6 +79,8 @@ class SpeckleDepthMaterial extends MeshDepthMaterial { ret.userData.uViewer_high = this.userData.uViewer_high ret.userData.uViewer_low = this.userData.uViewer_low ret.userData.rteModelViewMatrix = this.userData.rteModelViewMatrix + ret.userData.near = this.userData.near + ret.userData.far = this.userData.far return ret } @@ -84,6 +96,12 @@ class SpeckleDepthMaterial extends MeshDepthMaterial { this.userData.rteModelViewMatrix = { value: new Matrix4() } + this.userData.near = { + value: 0 + } + this.userData.far = { + value: 0 + } this.defines['USE_RTE'] = ' ' return this diff --git a/packages/viewer/src/modules/materials/shaders/speckle-apply-ao-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-apply-ao-frag.ts new file mode 100644 index 000000000..07603cd42 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-apply-ao-frag.ts @@ -0,0 +1,20 @@ +export const speckleApplyAoFrag = ` + uniform float opacity; + uniform sampler2D tDiffuse; + uniform sampler2D tDiffuseInterp; + varying vec2 vUv; + #if ACCUMULATE == 1 + uniform float frameIndex; + #endif + + void main() { + vec3 currentSample = texture2D( tDiffuse, vUv ).rgb; + + #if ACCUMULATE == 1 + vec3 interpSample = texture2D( tDiffuseInterp, vUv ).rgb; + gl_FragColor.rgb = mix(interpSample, currentSample, frameIndex/float(NUM_FRAMES)); + #else + gl_FragColor.rgb = currentSample; + #endif + gl_FragColor.a = 1.; + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-apply-ao-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-apply-ao-vert.ts new file mode 100644 index 000000000..7d241ade3 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-apply-ao-vert.ts @@ -0,0 +1,6 @@ +export const speckleApplyAoVert = ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-copy-output-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-copy-output-frag.ts new file mode 100644 index 000000000..f88de9c84 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-copy-output-frag.ts @@ -0,0 +1,30 @@ +export const speckleCopyOutputFrag = ` + uniform float opacity; + uniform sampler2D tDiffuse; + varying vec2 vUv; + + const float UnpackDownscale = 255. / 256.; + const vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. ); + const vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. ); + + float unpackRGBAToDepth( const in vec4 v ) { + return dot( v, UnpackFactors ); + } + + vec3 unpackRGBToNormal( const in vec3 rgb ) { + return 2.0 * rgb.xyz - 1.0; + } + + void main() { + vec4 inSample = texture2D( tDiffuse, vUv ); + vec3 outSample = inSample.rgb; + #if OUTPUT_TYPE == 1 + outSample.rgb = vec3(unpackRGBAToDepth(inSample)); + #endif + // #if OUTPUT_TYPE == 3 + // outSample.rgb = unpackRGBToNormal(inSample.rgb); + // #endif + + gl_FragColor.rgb = outSample; + gl_FragColor.a = 1.; + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-copy-output-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-copy-output-vert.ts new file mode 100644 index 000000000..7a083de37 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-copy-output-vert.ts @@ -0,0 +1,6 @@ +export const speckleCopyOutputVert = ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-depth-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-depth-frag.ts index 3d5ac0417..f3b144f99 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-depth-frag.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-depth-frag.ts @@ -2,6 +2,11 @@ export const speckleDepthFrag = /* glsl */ ` #if DEPTH_PACKING == 3200 uniform float opacity; #endif +#ifdef LINEAR_DEPTH + varying vec4 vViewPosition; + uniform float near; + uniform float far; +#endif #include #include #include @@ -11,6 +16,7 @@ export const speckleDepthFrag = /* glsl */ ` #include #include varying vec2 vHighPrecisionZW; + void main() { #include vec4 diffuseColor = vec4( 1.0 ); @@ -29,11 +35,16 @@ void main() { #endif #include // Higher precision equivalent of gl_FragCoord.z. This assumes depthRange has been left to its default values. - float fragCoordZ = (0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5); - #if DEPTH_PACKING == 3200 - gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity ); - #elif DEPTH_PACKING == 3201 - gl_FragColor = packDepthToRGBA( fragCoordZ ); + #ifdef LINEAR_DEPTH + /** View z is negative moving away from the camera */ + gl_FragColor = packDepthToRGBA((vViewPosition.z + near) / (near - far)); + #else + float fragCoordZ = (0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5); + #if DEPTH_PACKING == 3200 + gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity ); + #elif DEPTH_PACKING == 3201 + gl_FragColor = packDepthToRGBA( fragCoordZ ); + #endif #endif } ` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-depth-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-depth-vert.ts index 28db0f04c..c15b357e5 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-depth-vert.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-depth-vert.ts @@ -7,6 +7,9 @@ export const speckleDepthVert = /* glsl */ ` uniform vec3 uViewer_low; uniform mat4 rteModelViewMatrix; #endif +#ifdef LINEAR_DEPTH + varying vec4 vViewPosition; +#endif #include #include #include @@ -50,8 +53,13 @@ void main() { mvPosition = instanceMatrix * mvPosition; #endif + mvPosition = rteModelViewMatrix * mvPosition; + #ifdef LINEAR_DEPTH + vViewPosition = mvPosition; + #endif + gl_Position = projectionMatrix * mvPosition; #include // #include diff --git a/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts index b7c283856..787c83d6f 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts @@ -209,8 +209,22 @@ export const speckleSaoFrag = /* glsl */ ` } float centerViewZ = getViewZ( centerDepth ); vec3 viewPosition = getViewPosition( vUv, centerDepth, centerViewZ ); + + #ifdef OUTPUT_RECONSTRUCTED_NORMALS + vec3 normal; + #if IMPROVED_NORMAL_RECONSTRUCTION == 1 + normal = viewNormalImproved(vUv, viewPosition); + #elif ACCURATE_NORMAL_RECONSTRUCTION == 1 + normal = viewNormalAccurate(vUv, viewPosition, centerDepth); + #else + normal = normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) ); + #endif + gl_FragColor.rgb = packNormalToRGB(normal); + gl_FragColor.a = 1.; + return; + #endif + float ambientOcclusion = getAmbientOcclusion( viewPosition, centerDepth ); gl_FragColor = getDefaultColor( vUv ); - gl_FragColor.xyz *= 1.0 - ambientOcclusion; - // gl_FragColor.xyz = depth_cross(vUv, viewPosition) * 0.5 + 0.5; + gl_FragColor.xyz *= 1. - ambientOcclusion; }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-static-ao-accumulate-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-static-ao-accumulate-frag.ts new file mode 100644 index 000000000..728582776 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-static-ao-accumulate-frag.ts @@ -0,0 +1,11 @@ +export const speckleStaticAoAccumulateFrag = /* glsl */ ` + uniform float opacity; + uniform sampler2D tDiffuse; + varying vec2 vUv; + // #define NUM_FRAMES 16 + + void main() { + vec4 frameSample = texture2D( tDiffuse, vUv ); + gl_FragColor.xyz = frameSample.rgb * 1./float(NUM_FRAMES); + gl_FragColor.a = 1.;//*= opacity; + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-static-ao-accumulate-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-static-ao-accumulate-vert.ts new file mode 100644 index 000000000..30ad72282 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-static-ao-accumulate-vert.ts @@ -0,0 +1,6 @@ +export const speckleStaticAoAccumulateVert = /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-static-ao-generate-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-static-ao-generate-frag.ts new file mode 100644 index 000000000..83dba5c99 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-static-ao-generate-frag.ts @@ -0,0 +1,296 @@ +export const speckleStaticAoGenerateFrag = /* glsl */ ` + #include + varying vec2 vUv; + uniform sampler2D tDepth; + uniform sampler2D tNormal; + uniform vec2 size; + + uniform float cameraNear; + uniform float cameraFar; + uniform mat4 cameraProjectionMatrix; + uniform mat4 cameraInverseProjectionMatrix; + + uniform float scale; + uniform float intensity; + uniform float bias; + uniform float kernelRadius; + uniform float minResolution; + uniform float frameIndex; + uniform float tanFov; + + #define AO_ESTIMATOR 1 + // #define KERNEL_SIZE 16 + uniform sampler2D tNoise; + uniform vec3 kernel[ KERNEL_SIZE ]; + uniform float minDistance; + uniform float maxDistance; + + #define NUM_SAMPLES 16 + #define SPIRAL_TURNS 2 + + // #define NUM_FRAMES 16 + + #define NORMAL_TEXTURE 0 + #define IMPROVED_NORMAL_RECONSTRUCTION 0 + #define ACCURATE_NORMAL_RECONSTRUCTION 1 + + // RGBA depth + #include + vec4 getDefaultColor( const in vec2 screenPosition ) { + return vec4( 1.0 ); + } + + + float getLinearDepth( const in vec2 screenPosition ) { + return unpackRGBAToDepth( texture2D( tDepth, screenPosition ) ); + } + + float getPerspectiveDepth(const in vec2 coords) { + float linearDepth = unpackRGBAToDepth( texture2D( tDepth, coords ) ); + #if PERSPECTIVE_CAMERA == 1 + float viewZ = orthographicDepthToViewZ(linearDepth, cameraNear, cameraFar); + float centerDepth = viewZToPerspectiveDepth(viewZ, cameraNear, cameraFar); + return centerDepth; + #else + return linearDepth; + #endif + } + + float getViewDepth(const in float linearDepth) { + return orthographicDepthToViewZ(linearDepth, cameraNear, cameraFar); + } + + float getViewZ( const in float depth ) { + #if PERSPECTIVE_CAMERA == 1 + return perspectiveDepthToViewZ( depth, cameraNear, cameraFar ); + #else + return orthographicDepthToViewZ( depth, cameraNear, cameraFar ); + #endif + } + + vec3 getViewPosition( const in vec2 screenPosition, const in float depth, const in float viewZ ) { + float clipW = cameraProjectionMatrix[2][3] * viewZ + cameraProjectionMatrix[3][3]; + vec4 clipPosition = vec4( ( vec3( screenPosition, depth ) - 0.5 ) * 2.0, 1.0 ); + clipPosition *= clipW; // unprojection. + return ( cameraInverseProjectionMatrix * clipPosition ).xyz; + } + + //https://wickedengine.net/2019/09/22/improved-normal-reconstruction-from-depth/ + vec3 viewNormalImproved(in vec2 uv, in vec3 origin) + { + highp vec2 dd = abs(vec2(1./size.x, 1./size.y)); + highp vec2 ddx = vec2(dd.x, 0.); + highp vec2 ddy = vec2(0., dd.y); + + float sampleDepth = getPerspectiveDepth( uv - ddy ); + float sampleViewZ = getViewZ( sampleDepth ); + highp vec3 top = getViewPosition( uv - ddy, sampleDepth, sampleViewZ ); + + sampleDepth = getPerspectiveDepth( uv + ddy ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 bottom = getViewPosition( uv + ddy, sampleDepth, sampleViewZ ); + + highp vec3 center = origin; + + sampleDepth = getPerspectiveDepth( uv - ddx ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 left = getViewPosition( uv - ddx, sampleDepth, sampleViewZ ); + + sampleDepth = getPerspectiveDepth( uv + ddx ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 right = getViewPosition( uv + ddx, sampleDepth, sampleViewZ ); + + // get the difference between the current and each offset position + vec3 l = center - left; + vec3 r = right - center; + vec3 d = center - top; + vec3 u = bottom - center; + + // pick horizontal and vertical diff with the smallest z difference + vec3 hDeriv = abs(l.z) < abs(r.z) ? l : r; + vec3 vDeriv = abs(d.z) < abs(u.z) ? d : u; + + // get view space normal from the cross product of the two smallest offsets + vec3 viewNormal = normalize(cross(hDeriv, vDeriv)); + + return viewNormal; + } + + vec3 viewNormalAccurate(in vec2 uv, in vec3 origin, in float centerDepth) { + highp vec2 dd = abs(vec2(1./size.x, 1./size.y)); + highp vec2 ddx = vec2(dd.x, 0.); + highp vec2 ddy = vec2(0., dd.y); + + float sampleDepth = getPerspectiveDepth( uv - ddy ); + float sampleViewZ = getViewZ( sampleDepth ); + highp vec3 top = getViewPosition( uv - ddy, sampleDepth, sampleViewZ ); + + sampleDepth = getPerspectiveDepth( uv + ddy ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 bottom = getViewPosition( uv + ddy, sampleDepth, sampleViewZ ); + + highp vec3 center = origin; + + sampleDepth = getPerspectiveDepth( uv - ddx ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 left = getViewPosition( uv - ddx, sampleDepth, sampleViewZ ); + + sampleDepth = getPerspectiveDepth( uv + ddx ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 right = getViewPosition( uv + ddx, sampleDepth, sampleViewZ ); + + // get the difference between the current and each offset position + vec3 l = center - left; + vec3 r = right - center; + vec3 d = center - top; + vec3 u = bottom - center; + + // get depth values at 1 & 2 pixels offsets from current along the horizontal axis + vec4 H = vec4( + getLinearDepth(uv - ddx), + getLinearDepth(uv + ddx), + getLinearDepth(uv - 2. * ddx), + getLinearDepth(uv + 2. * ddx) + ); + + // get depth values at 1 & 2 pixels offsets from current along the vertical axis + vec4 V = vec4( + getLinearDepth(uv - ddy), + getLinearDepth(uv + ddy), + getLinearDepth(uv - 2. * ddy), + getLinearDepth(uv + 2. * ddy) + ); + + // current pixel's depth difference from slope of offset depth samples + // differs from original article because we're using non-linear depth values + // see article's comments + vec2 he = abs((2. * H.xy - H.zw) - centerDepth); + vec2 ve = abs((2. * V.xy - V.zw) - centerDepth); + + // pick horizontal and vertical diff with the smallest depth difference from slopes + vec3 hDeriv = he.x < he.y ? l : r; + vec3 vDeriv = ve.x < ve.y ? d : u; + + // get view space normal from the cross product of the best derivatives + vec3 viewNormal = normalize(cross(hDeriv, vDeriv)); + + return viewNormal; + + } + + vec3 getViewNormal( const in vec3 viewPosition, const in vec2 screenPosition, in float centerDepth ) { + #if NORMAL_TEXTURE == 1 + return unpackRGBToNormal( texture2D( tNormal, screenPosition ).xyz ); + #elif IMPROVED_NORMAL_RECONSTRUCTION == 1 + return viewNormalImproved(screenPosition, viewPosition); + #elif ACCURATE_NORMAL_RECONSTRUCTION == 1 + return viewNormalAccurate(screenPosition, viewPosition, centerDepth); + #else + return normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) ); + #endif + } + + + float scaleDividedByCameraFar; + float minResolutionMultipliedByCameraFar; + // moving costly divides into consts + const float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES ); + const float offset = PI2 / float(NUM_FRAMES); + + float computeKernelSize(float d, float r) { + #if PERSPECTIVE_CAMERA == 1 + // Apparently this is wrong + // return (r * tan(fov) * d) / (size.y * 0.5); + // And this is correct + float rp = r / (size.y * 0.5); + return sqrt((rp*rp*tanFov*tanFov*d*d)/(1. + rp*rp*tanFov*tanFov)); + #else + float twoOrthoSize = size.y / (2./ cameraProjectionMatrix[1][1]); + return r / twoOrthoSize; + #endif + } + + float getAmbientOcclusion( const in vec3 centerViewPosition, in float centerDepth ) { + #if AO_ESTIMATOR == 0 + // precompute some variables require in getOcclusion. + scaleDividedByCameraFar = scale / cameraFar; + minResolutionMultipliedByCameraFar = minResolution * cameraFar; + vec3 centerViewNormal = getViewNormal( centerViewPosition, vUv, centerDepth ); + // jsfiddle that shows sample pattern: https://jsfiddle.net/TenHands/jun67k9y/7/ + float occlusionSum = 0.0; + float weightSum = 0.0; + for( int i = 0; i < NUM_SAMPLES; i ++ ) { + float alpha = ( float(i) + 1. ) / float(NUM_SAMPLES); + float angle = float(SPIRAL_TURNS) * alpha; + vec2 radius = (kernelRadius / size) * pow( alpha, 1.1 ); + vec2 sampleUv = vUv + vec2( cos( angle + frameIndex * offset ), sin( angle + frameIndex * offset ) ) * radius; + + float sampleDepth = getPerspectiveDepth( sampleUv ); + if( sampleDepth >= ( 1.0 - EPSILON ) ) { + continue; + } + float sampleViewZ = getViewZ( sampleDepth ); + vec3 sampleViewPosition = getViewPosition( sampleUv, sampleDepth, sampleViewZ ); + + /** McGuire Estimator*/ + vec3 v = sampleViewPosition - centerViewPosition; + float vv = dot(v, v); + float vn = dot(v, centerViewNormal) - bias; + + // Note large epsilon to avoid overdarkening within cracks + float radius2 = 2.;//uSampleRadiusWS * uSampleRadiusWS + float epsilon = 0.01; + + float f = max(radius2 - vv, 0.0) / radius2; + occlusionSum += f * f * f * max(vn / (epsilon + vv), 0.0) / 4.; + + /** Three.js SAO Estimator*/ + // vec3 viewDelta = sampleViewPosition - centerViewPosition; + // float viewDistance = length( viewDelta ); + // float scaledScreenDistance = scaleDividedByCameraFar * viewDistance; + // occlusionSum += max(0.0, (dot(centerViewNormal, viewDelta) - minResolutionMultipliedByCameraFar) / scaledScreenDistance - bias) / (1.0 + pow2( scaledScreenDistance ) ); + weightSum += 1.0; + } + if( weightSum == 0.0 ) discard; + return occlusionSum * ( intensity / weightSum ); + #elif AO_ESTIMATOR == 1 + vec3 viewPosition = centerViewPosition; + vec3 viewNormal = getViewNormal( centerViewPosition, vUv, centerDepth ); + vec2 noiseScale = vec2( size.x / 4.0, size.y / 4.0 ); + vec3 random = vec3( texture2D( tNoise, vUv * noiseScale ).r ); + // compute matrix used to reorient a kernel vector + vec3 tangent = normalize( random - viewNormal * dot( random, viewNormal ) ); + vec3 bitangent = cross( viewNormal, tangent ); + mat3 kernelMatrix = mat3( tangent, bitangent, viewNormal ); + float occlusion = 0.0; + float kernelSize_ws = computeKernelSize(-viewPosition.z, kernelRadius); + for ( int i = 0; i < KERNEL_SIZE; i ++ ) { + vec3 sampleVector = kernelMatrix * kernel[ i ]; // reorient sample vector in view space + vec3 samplePoint = viewPosition + ( sampleVector * kernelSize_ws ); // calculate sample point + vec4 samplePointNDC = cameraProjectionMatrix * vec4( samplePoint, 1.0 ); // project point and calculate NDC + samplePointNDC /= samplePointNDC.w; + vec2 samplePointUv = samplePointNDC.xy * 0.5 + 0.5; // compute uv coordinates + float realDepth = getLinearDepth( samplePointUv ); // get linear depth from depth texture + float sampleDepth = viewZToOrthographicDepth( samplePoint.z + bias, cameraNear, cameraFar ); // compute linear depth of the sample view Z value + float delta = sampleDepth - realDepth; + if ( delta > minDistance && delta < maxDistance ) { // if fragment is before sample point, increase occlusion + occlusion += 1.0; + } + } + return clamp( occlusion * intensity / float( KERNEL_SIZE ), 0.0, 1.0 ); + #endif + } + void main() { + float linearDepth = unpackRGBAToDepth( texture2D( tDepth, vUv ) ); + float centerDepth = getPerspectiveDepth(vUv); + if( centerDepth >= ( 1.0 - EPSILON ) ) { + discard; + } + float centerViewZ = getViewDepth(linearDepth); + vec3 viewPosition = getViewPosition( vUv, centerDepth, centerViewZ ); + vec3 viewNormal = getViewNormal(viewPosition, vUv, linearDepth); + float ambientOcclusion = getAmbientOcclusion( viewPosition, centerDepth ); + gl_FragColor = getDefaultColor( vUv ); + gl_FragColor.xyz *= ambientOcclusion; + gl_FragColor.a = 1.; + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-static-ao-generate-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-static-ao-generate-vert.ts new file mode 100644 index 000000000..72263082b --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-static-ao-generate-vert.ts @@ -0,0 +1,6 @@ +export const speckleStaticAoGenerateVert = /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }` diff --git a/packages/viewer/src/modules/objects/SpeckleCameraControls.ts b/packages/viewer/src/modules/objects/SpeckleCameraControls.ts new file mode 100644 index 000000000..1b59bb151 --- /dev/null +++ b/packages/viewer/src/modules/objects/SpeckleCameraControls.ts @@ -0,0 +1,339 @@ +import CameraControls from 'camera-controls' +import { MathUtils, PerspectiveCamera, Vector3 } from 'three' + +let ACTION +;(function (ACTION) { + ACTION[(ACTION['NONE'] = 0)] = 'NONE' + ACTION[(ACTION['ROTATE'] = 1)] = 'ROTATE' + ACTION[(ACTION['TRUCK'] = 2)] = 'TRUCK' + ACTION[(ACTION['OFFSET'] = 3)] = 'OFFSET' + ACTION[(ACTION['DOLLY'] = 4)] = 'DOLLY' + ACTION[(ACTION['ZOOM'] = 5)] = 'ZOOM' + ACTION[(ACTION['TOUCH_ROTATE'] = 6)] = 'TOUCH_ROTATE' + ACTION[(ACTION['TOUCH_TRUCK'] = 7)] = 'TOUCH_TRUCK' + ACTION[(ACTION['TOUCH_OFFSET'] = 8)] = 'TOUCH_OFFSET' + ACTION[(ACTION['TOUCH_DOLLY'] = 9)] = 'TOUCH_DOLLY' + ACTION[(ACTION['TOUCH_ZOOM'] = 10)] = 'TOUCH_ZOOM' + ACTION[(ACTION['TOUCH_DOLLY_TRUCK'] = 11)] = 'TOUCH_DOLLY_TRUCK' + ACTION[(ACTION['TOUCH_DOLLY_OFFSET'] = 12)] = 'TOUCH_DOLLY_OFFSET' + ACTION[(ACTION['TOUCH_ZOOM_TRUCK'] = 13)] = 'TOUCH_ZOOM_TRUCK' + ACTION[(ACTION['TOUCH_ZOOM_OFFSET'] = 14)] = 'TOUCH_ZOOM_OFFSET' +})(ACTION || (ACTION = {})) +function isPerspectiveCamera(camera) { + return camera.isPerspectiveCamera +} +function isOrthographicCamera(camera) { + return camera.isOrthographicCamera +} + +const EPSILON = 1e-5 +function approxZero(number, error = EPSILON) { + return Math.abs(number) < error +} +function approxEquals(a, b, error = EPSILON) { + return approxZero(a - b, error) +} + +let _deltaTarget, _deltaOffset, _v3A, _v3B, _v3C +let _xColumn +let _yColumn +let _zColumn + +export class SpeckleCameraControls extends CameraControls { + private _didDolly = false + private _didDollyLastFrame = false + public _isTrucking = false + private _hasRestedLastFrame = false + static install() { + _v3A = new Vector3() + _v3B = new Vector3() + _v3C = new Vector3() + _xColumn = new Vector3() + _yColumn = new Vector3() + _zColumn = new Vector3() + _deltaTarget = new Vector3() + _deltaOffset = new Vector3() + } + + public get hasRested() { + return this._hasRested + } + + public set isTrucking(value: boolean) { + this._isTrucking = value + } + + protected _dollyInternal = (delta: number, x: number, y: number): void => { + const dollyScale = Math.pow(0.95, -delta * this.dollySpeed) + const distance = this._sphericalEnd.radius * dollyScale + const prevRadius = this._sphericalEnd.radius + const signedPrevRadius = prevRadius * (delta >= 0 ? -1 : 1) + + this.dollyTo(distance) + + if ( + this.infinityDolly && + (distance < this.minDistance || this.maxDistance === this.minDistance) + ) { + this._camera.getWorldDirection(_v3A) + this._targetEnd.add(_v3A.normalize().multiplyScalar(signedPrevRadius)) + this._target.add(_v3A.normalize().multiplyScalar(signedPrevRadius)) + } + + if (this.dollyToCursor) { + this._dollyControlAmount += this._sphericalEnd.radius - prevRadius + + if ( + this.infinityDolly && + (distance < this.minDistance || this.maxDistance === this.minDistance) + ) { + this._dollyControlAmount -= signedPrevRadius + } + + this._dollyControlCoord.set(x, y) + } + + return + } + + protected _zoomInternal = (delta: number, x: number, y: number): void => { + const zoomScale = Math.pow(0.95, delta * this.dollySpeed) + + // for both PerspectiveCamera and OrthographicCamera + this.zoomTo(this._zoom * zoomScale) + this._didDolly = true + this.dispatchEvent({ type: 'controlstart' }) + if (this.dollyToCursor) { + this._dollyControlAmount = this._zoomEnd + + this._dollyControlCoord.set(x, y) + } + + return + } + + /** + * Dolly in/out camera position to given distance. + * @param distance Distance of dolly. + * @param enableTransition Whether to move smoothly or immediately. + * @category Methods + */ + dollyTo(distance: number, enableTransition = true): Promise { + const lastRadius = this._sphericalEnd.radius + const newRadius = MathUtils.clamp(distance, this.minDistance, this.maxDistance) + const hasCollider = this.colliderMeshes.length >= 1 + + if (hasCollider) { + const maxDistanceByCollisionTest = this._collisionTest() + const isCollided = approxEquals( + maxDistanceByCollisionTest, + this._spherical.radius + ) + const isDollyIn = lastRadius > newRadius + + if (!isDollyIn && isCollided) return Promise.resolve() + + this._sphericalEnd.radius = Math.min(newRadius, maxDistanceByCollisionTest) + } else { + this._sphericalEnd.radius = newRadius + } + + this._needsUpdate = true + + if (!enableTransition) { + this._spherical.radius = this._sphericalEnd.radius + this._didDolly = true + this.dispatchEvent({ type: 'controlstart' }) + } + + const resolveImmediately = + !enableTransition || + approxEquals( + this._spherical.radius, + this._sphericalEnd.radius, + this.restThreshold + ) + return this._createOnRestPromise(resolveImmediately) + } + + update(delta) { + this._hasRestedLastFrame = this._hasRested + const dampingFactor = + this._state === ACTION.NONE ? this.dampingFactor : this.draggingDampingFactor + const dampingFactorDolly = 0.9 + const lerpRatio = Math.min(dampingFactor * delta * 60, 1) + const deltaTheta = this._sphericalEnd.theta - this._spherical.theta + const deltaPhi = this._sphericalEnd.phi - this._spherical.phi + const deltaRadius = this._sphericalEnd.radius - this._spherical.radius + const deltaTarget = _deltaTarget.subVectors(this._targetEnd, this._target) + const deltaOffset = _deltaOffset.subVectors(this._focalOffsetEnd, this._focalOffset) + if ( + !approxZero(deltaTheta) || + !approxZero(deltaPhi) || + !approxZero(deltaRadius) || + !approxZero(deltaTarget.x) || + !approxZero(deltaTarget.y) || + !approxZero(deltaTarget.z) || + !approxZero(deltaOffset.x) || + !approxZero(deltaOffset.y) || + !approxZero(deltaOffset.z) + ) { + this._spherical.set( + this._spherical.radius + deltaRadius * dampingFactorDolly, + this._spherical.phi + deltaPhi * lerpRatio, + this._spherical.theta + deltaTheta * lerpRatio + ) + this._target.add(deltaTarget.multiplyScalar(lerpRatio)) + this._focalOffset.add(deltaOffset.multiplyScalar(lerpRatio)) + this._needsUpdate = true + } else { + this._spherical.copy(this._sphericalEnd) + this._target.copy(this._targetEnd) + this._focalOffset.copy(this._focalOffsetEnd) + } + if (this._dollyControlAmount !== 0) { + if (isPerspectiveCamera(this._camera)) { + const camera = this._camera + const direction = _v3A + .setFromSpherical(this._sphericalEnd) + .applyQuaternion(this._yAxisUpSpaceInverse) + .normalize() + .negate() + const planeX = _v3B.copy(direction).cross(camera.up).normalize() + if (planeX.lengthSq() === 0) planeX.x = 1.0 + const planeY = _v3C.crossVectors(planeX, direction) + const worldToScreen = + this._sphericalEnd.radius * + Math.tan( + (camera as PerspectiveCamera).getEffectiveFOV() * MathUtils.DEG2RAD * 0.5 + ) + const prevRadius = this._sphericalEnd.radius - this._dollyControlAmount + const lerpRatio = + (prevRadius - this._sphericalEnd.radius) / this._sphericalEnd.radius + const cursor = _v3A + .copy(this._targetEnd) + .add( + planeX.multiplyScalar( + this._dollyControlCoord.x * + worldToScreen * + (camera as PerspectiveCamera).aspect + ) + ) + .add(planeY.multiplyScalar(this._dollyControlCoord.y * worldToScreen)) + this._targetEnd.lerp(cursor, lerpRatio) + this._target.copy(this._targetEnd) + } else if (isOrthographicCamera(this._camera)) { + const camera = this._camera + const worldPosition = _v3A + .set( + this._dollyControlCoord.x, + this._dollyControlCoord.y, + (camera.near + camera.far) / (camera.near - camera.far) + ) + .unproject(camera) + const quaternion = _v3B.set(0, 0, -1).applyQuaternion(camera.quaternion) + const divisor = quaternion.dot(camera.up) + const distance = approxZero(divisor) + ? -worldPosition.dot(camera.up) + : -worldPosition.dot(camera.up) / divisor + const cursor = _v3C.copy(worldPosition).add(quaternion.multiplyScalar(distance)) + this._targetEnd.lerp(cursor, 1 - camera.zoom / this._dollyControlAmount) + this._target.copy(this._targetEnd) + } + this._dollyControlAmount = 0 + } + const maxDistance = this._collisionTest() + this._spherical.radius = Math.min(this._spherical.radius, maxDistance) + this._spherical.makeSafe() + this._camera.position + .setFromSpherical(this._spherical) + .applyQuaternion(this._yAxisUpSpaceInverse) + .add(this._target) + this._camera.lookAt(this._target) + const affectOffset = + !approxZero(this._focalOffset.x) || + !approxZero(this._focalOffset.y) || + !approxZero(this._focalOffset.z) + if (affectOffset) { + this._camera.updateMatrix() + _xColumn.setFromMatrixColumn(this._camera.matrix, 0) + _yColumn.setFromMatrixColumn(this._camera.matrix, 1) + _zColumn.setFromMatrixColumn(this._camera.matrix, 2) + _xColumn.multiplyScalar(this._focalOffset.x) + _yColumn.multiplyScalar(-this._focalOffset.y) + _zColumn.multiplyScalar(this._focalOffset.z) + _v3A.copy(_xColumn).add(_yColumn).add(_zColumn) + this._camera.position.add(_v3A) + } + if (this._boundaryEnclosesCamera) { + this._encloseToBoundary( + this._camera.position.copy(this._target), + _v3A + .setFromSpherical(this._spherical) + .applyQuaternion(this._yAxisUpSpaceInverse), + 1.0 + ) + } + const zoomDelta = this._zoomEnd - this._zoom + this._zoom += zoomDelta * lerpRatio + if (this._camera.zoom !== this._zoom) { + if (approxZero(zoomDelta)) this._zoom = this._zoomEnd + this._camera.zoom = this._zoom + this._camera.updateProjectionMatrix() + this._updateNearPlaneCorners() + this._needsUpdate = true + } + const updated = this._needsUpdate + if (updated && !this._updatedLastTime) { + this._hasRested = false + this.dispatchEvent({ type: 'wake' }) + this.dispatchEvent({ type: 'update' }) + } else if (updated) { + this.dispatchEvent({ type: 'update' }) + if ( + approxZero(deltaTheta, this.restThreshold) && + approxZero(deltaPhi, this.restThreshold) && + approxZero(deltaRadius, this.restThreshold) && + approxZero(deltaTarget.x, this.restThreshold) && + approxZero(deltaTarget.y, this.restThreshold) && + approxZero(deltaTarget.z, this.restThreshold) && + approxZero(deltaOffset.x, this.restThreshold) && + approxZero(deltaOffset.y, this.restThreshold) && + approxZero(deltaOffset.z, this.restThreshold) && + !this._hasRested && + !this._isTrucking + ) { + this._hasRested = true + this.dispatchEvent({ type: 'rest' }) + } + } else if (!updated && this._updatedLastTime) { + this.dispatchEvent({ type: 'sleep' }) + } + if (this._didDollyLastFrame) { + if ( + approxZero(deltaTheta, this.restThreshold) && + approxZero(deltaPhi, this.restThreshold) && + approxZero(deltaRadius, this.restThreshold) && + approxZero(deltaTarget.x, this.restThreshold) && + approxZero(deltaTarget.y, this.restThreshold) && + approxZero(deltaTarget.z, this.restThreshold) && + approxZero(deltaOffset.x, this.restThreshold) && + approxZero(deltaOffset.y, this.restThreshold) && + approxZero(deltaOffset.z, this.restThreshold) && + !this._isTrucking + ) { + this.dispatchEvent({ type: 'rest' }) + this._didDollyLastFrame = false + } + } + + if (this._didDolly) { + this._didDolly = false + this._didDollyLastFrame = true + } + + this._updatedLastTime = updated + this._needsUpdate = false + return updated && !this._hasRested + } +} diff --git a/packages/viewer/src/modules/objects/SpeckleRaycaster.ts b/packages/viewer/src/modules/objects/SpeckleRaycaster.ts index 1a400f647..a7907b59f 100644 --- a/packages/viewer/src/modules/objects/SpeckleRaycaster.ts +++ b/packages/viewer/src/modules/objects/SpeckleRaycaster.ts @@ -1,10 +1,12 @@ import { Object3D, Raycaster } from 'three' +import { ObjectLayers } from '../SpeckleRenderer' export class SpeckleRaycaster extends Raycaster { public onObjectIntersectionTest: (object: Object3D) => void = null constructor(origin?, direction?, near = 0, far = Infinity) { super(origin, direction, near, far) + this.layers.set(ObjectLayers.STREAM_CONTENT) } public intersectObjects(objects, recursive = true, intersects = []) { diff --git a/packages/viewer/src/modules/pipeline/ApplyAOPass.ts b/packages/viewer/src/modules/pipeline/ApplyAOPass.ts new file mode 100644 index 000000000..ad1ca0fd9 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/ApplyAOPass.ts @@ -0,0 +1,109 @@ +import { + AddEquation, + Camera, + CustomBlending, + DstAlphaFactor, + DstColorFactor, + NoBlending, + Scene, + ShaderMaterial, + Texture, + ZeroFactor +} from 'three' +import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass' +import { speckleApplyAoFrag } from '../materials/shaders/speckle-apply-ao-frag' +import { speckleApplyAoVert } from '../materials/shaders/speckle-apply-ao-vert' +import { Pipeline, RenderType } from './Pipeline' +import { + InputColorTextureUniform, + InputColorInterpolateTextureUniform, + SpeckleProgressivePass +} from './SpecklePass' + +export class ApplySAOPass extends Pass implements SpeckleProgressivePass { + private fsQuad: FullScreenQuad + public materialCopy: ShaderMaterial + private frameIndex = 0 + + constructor() { + super() + this.materialCopy = new ShaderMaterial({ + defines: { + ACCUMULATE: 0 + }, + uniforms: { + tDiffuse: { value: null }, + tDiffuseInterp: { value: null }, + frameIndex: { value: 0 } + }, + vertexShader: speckleApplyAoVert, + fragmentShader: speckleApplyAoFrag, + blending: NoBlending + }) + this.materialCopy.transparent = true + this.materialCopy.depthTest = false + this.materialCopy.depthWrite = false + this.materialCopy.blending = CustomBlending + this.materialCopy.blendSrc = DstColorFactor + this.materialCopy.blendDst = ZeroFactor + this.materialCopy.blendEquation = AddEquation + this.materialCopy.blendSrcAlpha = DstAlphaFactor + this.materialCopy.blendDstAlpha = ZeroFactor + this.materialCopy.blendEquationAlpha = AddEquation + + this.materialCopy.needsUpdate = true + this.fsQuad = new FullScreenQuad(this.materialCopy) + } + + public setTexture( + uName: InputColorTextureUniform | InputColorInterpolateTextureUniform, + texture: Texture + ) { + this.materialCopy.uniforms[uName].value = texture + this.materialCopy.needsUpdate = true + } + + get displayName(): string { + return 'APPLYSAO' + } + + get outputTexture(): Texture { + return null + } + + setParams(params: unknown) { + params + } + + setFrameIndex(index: number) { + this.frameIndex = index + } + + setRenderType(type: RenderType) { + if (type === RenderType.NORMAL) { + this.materialCopy.defines['ACCUMULATE'] = 0 + } else { + this.materialCopy.defines['ACCUMULATE'] = 1 + this.frameIndex = 0 + } + this.materialCopy.needsUpdate = true + } + + public update(scene: Scene, camera: Camera) { + scene + camera + this.materialCopy.defines['NUM_FRAMES'] = Pipeline.ACCUMULATE_FRAMES + this.materialCopy.uniforms['frameIndex'].value = this.frameIndex + this.materialCopy.needsUpdate = true + } + + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { + writeBuffer + readBuffer + renderer.setRenderTarget(null) + const rendereAutoClear = renderer.autoClear + renderer.autoClear = false + this.fsQuad.render(renderer) + renderer.autoClear = rendereAutoClear + } +} diff --git a/packages/viewer/src/modules/pipeline/ApplySAOPass.ts b/packages/viewer/src/modules/pipeline/ApplySAOPass.ts deleted file mode 100644 index cbddfdc58..000000000 --- a/packages/viewer/src/modules/pipeline/ApplySAOPass.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { - AddEquation, - CustomBlending, - DstAlphaFactor, - DstColorFactor, - NoBlending, - ShaderMaterial, - Texture, - UniformsUtils, - ZeroFactor -} from 'three' -import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass' -import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js' - -export class ApplySAOPass extends Pass { - private fsQuad: FullScreenQuad - private materialCopy: ShaderMaterial - - constructor(srcSao: Texture) { - super() - this.materialCopy = new ShaderMaterial({ - uniforms: UniformsUtils.clone(CopyShader.uniforms), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - blending: NoBlending - }) - this.materialCopy.transparent = true - this.materialCopy.depthTest = false - this.materialCopy.depthWrite = false - this.materialCopy.blending = CustomBlending - this.materialCopy.blendSrc = DstColorFactor - this.materialCopy.blendDst = ZeroFactor - this.materialCopy.blendEquation = AddEquation - this.materialCopy.blendSrcAlpha = DstAlphaFactor - this.materialCopy.blendDstAlpha = ZeroFactor - this.materialCopy.blendEquationAlpha = AddEquation - this.materialCopy.uniforms['tDiffuse'].value = srcSao - this.materialCopy.needsUpdate = true - this.fsQuad = new FullScreenQuad(this.materialCopy) - } - - render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { - writeBuffer - readBuffer - renderer.setRenderTarget(null) - this.fsQuad.render(renderer) - } -} diff --git a/packages/viewer/src/modules/pipeline/ColorPass.ts b/packages/viewer/src/modules/pipeline/ColorPass.ts new file mode 100644 index 000000000..3a477aa03 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/ColorPass.ts @@ -0,0 +1,74 @@ +import { Camera, Color, Material, Scene, Texture } from 'three' +import { BaseSpecklePass, SpecklePass } from './SpecklePass' + +export class ColorPass extends BaseSpecklePass implements SpecklePass { + private camera: Camera + private scene: Scene + private overrideMaterial: Material = null + private _oldClearColor: Color = new Color() + private clearColor: Color = null + private clearAlpha = 0 + private clearDepth = true + + public constructor() { + super() + } + public get displayName(): string { + return 'COLOR' + } + public get outputTexture(): Texture { + return null + } + + public update(scene: Scene, camera: Camera) { + this.camera = camera + this.scene = scene + } + + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { + const oldAutoClear = renderer.autoClear + renderer.autoClear = false + + let oldClearAlpha, oldOverrideMaterial + + if (this.overrideMaterial !== undefined) { + oldOverrideMaterial = this.scene.overrideMaterial + + this.scene.overrideMaterial = this.overrideMaterial + } + + if (this.clearColor) { + renderer.getClearColor(this._oldClearColor) + oldClearAlpha = renderer.getClearAlpha() + + renderer.setClearColor(this.clearColor, this.clearAlpha) + } + + if (this.clearDepth) { + renderer.clearDepth() + } + + this.applyLayers(this.camera) + + renderer.setRenderTarget(this.renderToScreen ? null : readBuffer) + + // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 + if (this.clear) + renderer.clear( + renderer.autoClearColor, + renderer.autoClearDepth, + renderer.autoClearStencil + ) + renderer.render(this.scene, this.camera) + + if (this.clearColor) { + renderer.setClearColor(this._oldClearColor, oldClearAlpha) + } + + if (this.overrideMaterial !== undefined) { + this.scene.overrideMaterial = oldOverrideMaterial + } + + renderer.autoClear = oldAutoClear + } +} diff --git a/packages/viewer/src/modules/pipeline/CopyOutputPass.ts b/packages/viewer/src/modules/pipeline/CopyOutputPass.ts new file mode 100644 index 000000000..076b213e0 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/CopyOutputPass.ts @@ -0,0 +1,56 @@ +import { NoBlending, ShaderMaterial, Texture, UniformsUtils } from 'three' +import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass' +import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js' +import { speckleCopyOutputFrag } from '../materials/shaders/speckle-copy-output-frag' +import { speckleCopyOutputVert } from '../materials/shaders/speckle-copy-output-vert' +import { PipelineOutputType } from './Pipeline' +import { InputColorTextureUniform, SpecklePass } from './SpecklePass' + +export class CopyOutputPass extends Pass implements SpecklePass { + private fsQuad: FullScreenQuad + public materialCopy: ShaderMaterial + + constructor() { + super() + this.materialCopy = new ShaderMaterial({ + defines: { + INPUT_TYPE: 0 + }, + uniforms: UniformsUtils.clone(CopyShader.uniforms), + vertexShader: speckleCopyOutputVert, + fragmentShader: speckleCopyOutputFrag, + blending: NoBlending + }) + + this.materialCopy.needsUpdate = true + this.fsQuad = new FullScreenQuad(this.materialCopy) + } + + public setOutputType(type: PipelineOutputType) { + this.materialCopy.defines['OUTPUT_TYPE'] = type + this.materialCopy.needsUpdate = true + } + + public setTexture(uName: InputColorTextureUniform, texture: Texture) { + this.materialCopy.uniforms[uName].value = texture + this.materialCopy.needsUpdate = true + } + + get displayName(): string { + return 'COPY-OUTPUT' + } + + get outputTexture(): Texture { + return null + } + + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { + writeBuffer + readBuffer + renderer.setRenderTarget(null) + const rendereAutoClear = renderer.autoClear + renderer.autoClear = false + this.fsQuad.render(renderer) + renderer.autoClear = rendereAutoClear + } +} diff --git a/packages/viewer/src/modules/pipeline/DepthPass.ts b/packages/viewer/src/modules/pipeline/DepthPass.ts new file mode 100644 index 000000000..fa4ff4824 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/DepthPass.ts @@ -0,0 +1,153 @@ +import { + Camera, + Color, + DoubleSide, + NearestFilter, + NoBlending, + OrthographicCamera, + PerspectiveCamera, + Plane, + RGBADepthPacking, + Scene, + Texture, + WebGLRenderTarget +} from 'three' +import SpeckleDepthMaterial from '../materials/SpeckleDepthMaterial' +import { BaseSpecklePass, SpecklePass } from './SpecklePass' + +export enum DepthType { + PERSPECTIVE_DEPTH, + LINEAR_DEPTH +} + +export enum DepthSize { + FULL, + HALF +} + +export class DepthPass extends BaseSpecklePass implements SpecklePass { + private renderTarget: WebGLRenderTarget + private renderTargetHalf: WebGLRenderTarget + private depthMaterial: SpeckleDepthMaterial = null + private depthBufferSize: DepthSize = DepthSize.FULL + private scene: Scene + private camera: Camera + + private colorBuffer: Color = new Color() + + public onBeforeRender: () => void = null + public onAfterRender: () => void = null + + get displayName(): string { + return 'DEPTH' + } + + get outputTexture(): Texture { + return this.renderTarget.texture + } + + get outputTextureHalf(): Texture { + return this.renderTargetHalf.texture + } + + public set depthType(value: DepthType) { + if (value === DepthType.LINEAR_DEPTH) + this.depthMaterial.defines['LINEAR_DEPTH'] = ' ' + else delete this.depthMaterial.defines['LINEAR_DEPTH'] + this.depthMaterial.needsUpdate = true + } + + public set depthSize(value: DepthSize) { + this.depthBufferSize = value + } + + constructor() { + super() + + this.renderTarget = new WebGLRenderTarget(256, 256, { + minFilter: NearestFilter, + magFilter: NearestFilter + }) + this.renderTargetHalf = new WebGLRenderTarget(256, 256, { + minFilter: NearestFilter, + magFilter: NearestFilter + }) + + /** On Chromium, on MacOS the 16 bit depth render buffer appears broken. + * We're not really using a stencil buffer at all, we're just forcing + * three.js to use a 24 bit depth render buffer + */ + this.renderTarget.depthBuffer = true + this.renderTarget.stencilBuffer = true + this.renderTargetHalf.depthBuffer = true + this.renderTargetHalf.stencilBuffer = true + + this.depthMaterial = new SpeckleDepthMaterial( + { + depthPacking: RGBADepthPacking + }, + ['USE_RTE', 'ALPHATEST_REJECTION'] + ) + this.depthMaterial.blending = NoBlending + this.depthMaterial.side = DoubleSide + } + + public setClippingPlanes(planes: Plane[]) { + this.depthMaterial.clippingPlanes = planes + } + + public update(scene: Scene, camera: Camera) { + this.camera = camera + this.scene = scene + this.depthMaterial.userData.near.value = ( + camera as PerspectiveCamera | OrthographicCamera + ).near + this.depthMaterial.userData.far.value = ( + camera as PerspectiveCamera | OrthographicCamera + ).far + this.depthMaterial.needsUpdate = true + } + + public render(renderer, writeBuffer, readBuffer) { + writeBuffer + readBuffer + + this.onBeforeRender() + renderer.getClearColor(this.colorBuffer) + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear + + renderer.setRenderTarget( + this.depthBufferSize === DepthSize.FULL + ? this.renderTarget + : this.renderTargetHalf + ) + renderer.autoClear = false + + renderer.setClearColor(0x000000) + renderer.setClearAlpha(1.0) + renderer.clear() + + const shadowmapEnabled = renderer.shadowMap.enabled + const shadowmapNeedsUpdate = renderer.shadowMap.needsUpdate + this.scene.overrideMaterial = this.depthMaterial + renderer.shadowMap.enabled = false + renderer.shadowMap.needsUpdate = false + this.applyLayers(this.camera) + renderer.render(this.scene, this.camera) + renderer.shadowMap.enabled = shadowmapEnabled + renderer.shadowMap.needsUpdate = shadowmapNeedsUpdate + this.scene.overrideMaterial = null + + // restore original state + renderer.autoClear = originalAutoClear + renderer.setClearColor(this.colorBuffer) + renderer.setClearAlpha(originalClearAlpha) + this.onAfterRender() + } + + public setSize(width: number, height: number) { + this.renderTarget.setSize(width, height) + this.renderTargetHalf.setSize(width * 0.5, height * 0.5) + } +} diff --git a/packages/viewer/src/modules/pipeline/DynamicAOPass.ts b/packages/viewer/src/modules/pipeline/DynamicAOPass.ts new file mode 100644 index 000000000..dda11ba67 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/DynamicAOPass.ts @@ -0,0 +1,312 @@ +import { + Camera, + Color, + NoBlending, + OrthographicCamera, + PerspectiveCamera, + Scene, + ShaderMaterial, + Texture, + UniformsUtils, + Vector2, + WebGLRenderTarget +} from 'three' +import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass' +import { speckleSaoFrag } from '../materials/shaders/speckle-sao-frag' +import { speckleSaoVert } from '../materials/shaders/speckle-sao-vert' +import { SAOShader } from 'three/examples/jsm/shaders/SAOShader.js' +import { DepthLimitedBlurShader } from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js' +import { BlurShaderUtils } from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js' +import { + InputDepthTextureUniform, + InputNormalsTextureUniform, + SpecklePass +} from './SpecklePass' + +export enum NormalsType { + DEFAULT = 0, + IMPROVED = 1, + ACCURATE = 2 +} + +export enum DynamicAOOutputType { + RECONSTRUCTED_NORMALS, + AO, + AO_BLURRED +} + +export interface DynamicAOPassParams { + intensity: number + scale: number + kernelRadius: number + bias: number + normalsType: NormalsType + blurEnabled: boolean + blurRadius: number + blurStdDev: number + blurDepthCutoff: number +} + +export const DefaultDynamicAOPassParams = { + intensity: 1.5, + scale: 0, + kernelRadius: 5, + bias: 0.2, + normalsType: NormalsType.ACCURATE, + blurEnabled: true, + blurRadius: 2, + blurStdDev: 4, + blurDepthCutoff: 0.007 +} + +export class DynamicSAOPass extends Pass implements SpecklePass { + private params: DynamicAOPassParams = DefaultDynamicAOPassParams + private colorBuffer: Color = new Color() + private saoMaterial: ShaderMaterial = null + private vBlurMaterial: ShaderMaterial = null + private hBlurMaterial: ShaderMaterial = null + private saoRenderTarget: WebGLRenderTarget = null + private blurIntermediateRenderTarget: WebGLRenderTarget = null + private fsQuad: FullScreenQuad = null + private _outputType: DynamicAOOutputType = DynamicAOOutputType.AO_BLURRED + private outputScale = 0.5 + + private prevStdDev: number + private prevNumSamples: number + + public get displayName(): string { + return 'SAO' + } + + public get outputTexture(): Texture { + return this.saoRenderTarget.texture + } + + constructor() { + super() + + this.saoRenderTarget = new WebGLRenderTarget(256, 256) + this.blurIntermediateRenderTarget = new WebGLRenderTarget(256, 256) + this.saoMaterial = new ShaderMaterial({ + defines: { + NUM_SAMPLES: 7, + NUM_RINGS: 4, + NORMAL_TEXTURE: 0, + DIFFUSE_TEXTURE: 0, + DEPTH_PACKING: 1, + PERSPECTIVE_CAMERA: 1 + }, + fragmentShader: speckleSaoFrag, + vertexShader: speckleSaoVert, + uniforms: UniformsUtils.clone(SAOShader.uniforms) + }) + this.saoMaterial.extensions.derivatives = true + this.saoMaterial.defines['DEPTH_PACKING'] = 1 + this.saoMaterial.uniforms['tDepth'].value = null + this.saoMaterial.uniforms['tNormal'].value = null + this.saoMaterial.uniforms['size'].value.set(256, 256) + this.saoMaterial.uniforms['minResolution'].value = 0 + this.saoMaterial.blending = NoBlending + + this.vBlurMaterial = new ShaderMaterial({ + uniforms: UniformsUtils.clone(DepthLimitedBlurShader.uniforms), + defines: Object.assign({}, DepthLimitedBlurShader.defines), + vertexShader: DepthLimitedBlurShader.vertexShader, + fragmentShader: DepthLimitedBlurShader.fragmentShader + }) + this.vBlurMaterial.defines['DEPTH_PACKING'] = 1 + + this.vBlurMaterial.uniforms['tDiffuse'].value = this.saoRenderTarget.texture + this.vBlurMaterial.uniforms['tDepth'].value = null + this.vBlurMaterial.uniforms['size'].value.set(256, 256) + this.vBlurMaterial.blending = NoBlending + + this.hBlurMaterial = new ShaderMaterial({ + uniforms: UniformsUtils.clone(DepthLimitedBlurShader.uniforms), + defines: Object.assign({}, DepthLimitedBlurShader.defines), + vertexShader: DepthLimitedBlurShader.vertexShader, + fragmentShader: DepthLimitedBlurShader.fragmentShader + }) + this.hBlurMaterial.defines['DEPTH_PACKING'] = 1 + + this.hBlurMaterial.uniforms['tDiffuse'].value = + this.blurIntermediateRenderTarget.texture + this.hBlurMaterial.uniforms['tDepth'].value = null + this.hBlurMaterial.uniforms['size'].value.set(256, 256) + this.hBlurMaterial.blending = NoBlending + + this.fsQuad = new FullScreenQuad(this.saoMaterial) + } + + public setParams(params: unknown) { + Object.assign(this.params, params) + } + + public setOutputType(type: DynamicAOOutputType) { + this._outputType = type + } + + public setTexture( + uName: InputDepthTextureUniform | InputNormalsTextureUniform, + texture: Texture + ) { + if (uName === 'tDepth') { + this.saoMaterial.uniforms['tDepth'].value = texture + this.vBlurMaterial.uniforms['tDepth'].value = texture + this.hBlurMaterial.uniforms['tDepth'].value = texture + } + if (uName === 'tNormal') { + this.saoMaterial.uniforms['tNormal'].value = texture + } + this.saoMaterial.needsUpdate = true + this.vBlurMaterial.needsUpdate = true + this.hBlurMaterial.needsUpdate = true + } + + public update(scene: Scene, camera: Camera) { + if (this._outputType === DynamicAOOutputType.RECONSTRUCTED_NORMALS) { + this.saoMaterial.defines['OUTPUT_RECONSTRUCTED_NORMALS'] = '' + } else { + delete this.saoMaterial.defines['OUTPUT_RECONSTRUCTED_NORMALS'] + } + + this.params.scale = (camera as PerspectiveCamera | OrthographicCamera).far + /** SAO DEFINES */ + this.saoMaterial.defines['PERSPECTIVE_CAMERA'] = (camera as PerspectiveCamera) + .isPerspectiveCamera + ? 1 + : 0 + + this.saoMaterial.defines['NORMAL_TEXTURE'] = + this.params.normalsType === NormalsType.DEFAULT ? 1 : 0 + this.saoMaterial.defines['IMPROVED_NORMAL_RECONSTRUCTION'] = + this.params.normalsType === NormalsType.IMPROVED ? 1 : 0 + this.saoMaterial.defines['ACCURATE_NORMAL_RECONSTRUCTION'] = + this.params.normalsType === NormalsType.ACCURATE ? 1 : 0 + + /** SAO UNIFORMS */ + this.saoMaterial.uniforms['cameraNear'].value = ( + camera as PerspectiveCamera | OrthographicCamera + ).near + this.saoMaterial.uniforms['cameraFar'].value = ( + camera as PerspectiveCamera | OrthographicCamera + ).far + this.saoMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy( + camera.projectionMatrixInverse + ) + this.saoMaterial.uniforms['cameraProjectionMatrix'].value = camera.projectionMatrix + + /** SAO UNIFORM PARAMS */ + this.saoMaterial.uniforms['intensity'].value = this.params.intensity + this.saoMaterial.uniforms['scale'].value = this.params.scale + this.saoMaterial.uniforms['kernelRadius'].value = this.params.kernelRadius + this.saoMaterial.uniforms['bias'].value = this.params.bias + + this.saoMaterial.needsUpdate = true + + /** BLUR DEFINES */ + this.vBlurMaterial.defines['PERSPECTIVE_CAMERA'] = (camera as PerspectiveCamera) + .isPerspectiveCamera + ? 1 + : 0 + this.hBlurMaterial.defines['PERSPECTIVE_CAMERA'] = (camera as PerspectiveCamera) + .isPerspectiveCamera + ? 1 + : 0 + + /** BLUR UNIFORMS */ + this.vBlurMaterial.uniforms['cameraNear'].value = ( + camera as PerspectiveCamera | OrthographicCamera + ).near + this.vBlurMaterial.uniforms['cameraFar'].value = ( + camera as PerspectiveCamera | OrthographicCamera + ).far + this.hBlurMaterial.uniforms['cameraNear'].value = ( + camera as PerspectiveCamera | OrthographicCamera + ).near + this.hBlurMaterial.uniforms['cameraFar'].value = ( + camera as PerspectiveCamera | OrthographicCamera + ).far + + /** BLUR UNIFORM PARAMS */ + const depthCutoff = + this.params.blurDepthCutoff * + ((camera as PerspectiveCamera | OrthographicCamera).far - + (camera as PerspectiveCamera | OrthographicCamera).near) + this.vBlurMaterial.uniforms['depthCutoff'].value = depthCutoff + this.hBlurMaterial.uniforms['depthCutoff'].value = depthCutoff + + this.params.blurRadius = Math.floor(this.params.blurRadius) + if ( + this.prevStdDev !== this.params.blurStdDev || + this.prevNumSamples !== this.params.blurRadius + ) { + BlurShaderUtils.configure( + this.vBlurMaterial, + this.params.blurRadius, + this.params.blurStdDev, + new Vector2(0, 1) + ) + BlurShaderUtils.configure( + this.hBlurMaterial, + this.params.blurRadius, + this.params.blurStdDev, + new Vector2(1, 0) + ) + this.prevStdDev = this.params.blurStdDev + this.prevNumSamples = this.params.blurRadius + } + this.vBlurMaterial.needsUpdate = true + this.hBlurMaterial.needsUpdate = true + } + + public render(renderer) { + // Rendering SAO texture + renderer.getClearColor(this.colorBuffer) + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear + + renderer.setRenderTarget(this.saoRenderTarget) + + // setup pass state + renderer.autoClear = false + renderer.setClearColor(0xffffff) + renderer.setClearAlpha(1.0) + renderer.clear() + this.fsQuad.material = this.saoMaterial + this.fsQuad.render(renderer) + + if ( + this.params.blurEnabled && + this._outputType === DynamicAOOutputType.AO_BLURRED + ) { + renderer.setRenderTarget(this.blurIntermediateRenderTarget) + renderer.setClearColor(0xffffff) + renderer.setClearAlpha(1.0) + renderer.clear() + this.fsQuad.material = this.vBlurMaterial + this.fsQuad.render(renderer) + + renderer.setRenderTarget(this.saoRenderTarget) + this.fsQuad.material = this.hBlurMaterial + this.fsQuad.render(renderer) + } + + // restore original state + renderer.autoClear = originalAutoClear + renderer.setClearColor(this.colorBuffer) + renderer.setClearAlpha(originalClearAlpha) + } + + public setSize(inputWidth: number, inputHeight: number) { + const width = inputWidth * this.outputScale + const height = inputHeight * this.outputScale + this.saoRenderTarget.setSize(width, height) + this.blurIntermediateRenderTarget.setSize(width, height) + + this.saoMaterial.uniforms['size'].value.set(width, height) + this.vBlurMaterial.uniforms['size'].value.set(width, height) + this.hBlurMaterial.uniforms['size'].value.set(width, height) + this.saoMaterial.needsUpdate = true + } +} diff --git a/packages/viewer/src/modules/pipeline/NormalsPass.ts b/packages/viewer/src/modules/pipeline/NormalsPass.ts new file mode 100644 index 000000000..d4aa86872 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/NormalsPass.ts @@ -0,0 +1,95 @@ +import { + Camera, + Color, + DoubleSide, + NoBlending, + Plane, + Scene, + Texture, + WebGLRenderTarget +} from 'three' +import SpeckleNormalMaterial from '../materials/SpeckleNormalMaterial' +import { BaseSpecklePass, SpecklePass } from './SpecklePass' + +export class NormalsPass extends BaseSpecklePass implements SpecklePass { + private renderTarget: WebGLRenderTarget + private normalsMaterial: SpeckleNormalMaterial = null + private scene: Scene + private camera: Camera + + private colorBuffer: Color = new Color() + + public onBeforeRender: () => void = null + public onAfterRender: () => void = null + + get displayName(): string { + return 'GEOMETRY-NORMALS' + } + + get outputTexture(): Texture { + return this.renderTarget.texture + } + + constructor() { + super() + + this.renderTarget = new WebGLRenderTarget(256, 256) + /** On Chromium, on MacOS the 16 bit depth render buffer appears broken. + * We're not really using a stencil buffer at all, we're just forcing + * three.js to use a 24 bit depth render buffer + */ + this.renderTarget.depthBuffer = true + this.renderTarget.stencilBuffer = true + + this.normalsMaterial = new SpeckleNormalMaterial({}, ['USE_RTE']) + this.normalsMaterial.blending = NoBlending + this.normalsMaterial.side = DoubleSide + } + + public setClippingPlanes(planes: Plane[]) { + this.normalsMaterial.clippingPlanes = planes + } + + public update(scene: Scene, camera: Camera) { + this.camera = camera + this.scene = scene + } + + public render(renderer, writeBuffer, readBuffer) { + writeBuffer + readBuffer + + this.onBeforeRender() + renderer.getClearColor(this.colorBuffer) + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear + + renderer.setRenderTarget(this.renderTarget) + renderer.autoClear = false + + renderer.setClearColor(0x000000) + renderer.setClearAlpha(1.0) + renderer.clear() + + const shadowmapEnabled = renderer.shadowMap.enabled + const shadowmapNeedsUpdate = renderer.shadowMap.needsUpdate + this.scene.overrideMaterial = this.normalsMaterial + renderer.shadowMap.enabled = false + renderer.shadowMap.needsUpdate = false + this.applyLayers(this.camera) + renderer.render(this.scene, this.camera) + renderer.shadowMap.enabled = shadowmapEnabled + renderer.shadowMap.needsUpdate = shadowmapNeedsUpdate + this.scene.overrideMaterial = null + + // restore original state + renderer.autoClear = originalAutoClear + renderer.setClearColor(this.colorBuffer) + renderer.setClearAlpha(originalClearAlpha) + this.onAfterRender() + } + + public setSize(width: number, height: number) { + this.renderTarget.setSize(width, height) + } +} diff --git a/packages/viewer/src/modules/pipeline/Pipeline.ts b/packages/viewer/src/modules/pipeline/Pipeline.ts index e6991a1b4..ecf67f272 100644 --- a/packages/viewer/src/modules/pipeline/Pipeline.ts +++ b/packages/viewer/src/modules/pipeline/Pipeline.ts @@ -1,53 +1,215 @@ -import { Camera, Plane, Scene, Vector2, WebGLRenderer } from 'three' -import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' -import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js' -import { SAOPassParams } from 'three/examples/jsm/postprocessing/SAOPass.js' +import { Plane, Vector2, WebGLRenderer } from 'three' +import { + EffectComposer, + Pass +} from 'three/examples/jsm/postprocessing/EffectComposer.js' import Batcher from '../batching/Batcher' -import { ApplySAOPass } from './ApplySAOPass' -import { NormalsType, SpeckleSAOPass } from './SpeckleSAOPass' +import SpeckleRenderer, { ObjectLayers } from '../SpeckleRenderer' +import { ApplySAOPass } from './ApplyAOPass' +import { CopyOutputPass } from './CopyOutputPass' +import { DepthPass, DepthSize, DepthType } from './DepthPass' +import { NormalsPass } from './NormalsPass' +import { + DefaultDynamicAOPassParams, + DynamicSAOPass, + DynamicAOOutputType, + DynamicAOPassParams, + NormalsType +} from './DynamicAOPass' +import { + DefaultStaticAoPassParams, + StaticAOPass, + StaticAoPassParams +} from './StaticAOPass' +import { SpecklePass } from './SpecklePass' +import { ColorPass } from './ColorPass' + +export enum RenderType { + NORMAL, + ACCUMULATION +} + +export enum PipelineOutputType { + DEPTH_RGBA = 0, + DEPTH = 1, + COLOR = 2, + GEOMETRY_NORMALS = 3, + RECONSTRUCTED_NORMALS = 4, + DYNAMIC_AO = 5, + DYNAMIC_AO_BLURED = 6, + PROGRESSIVE_AO = 7, + FINAL = 8 +} export interface PipelineOptions { - saoEnabled?: boolean - saoParams?: Partial - saoScaleOffset?: number - saoNormalsRendering?: NormalsType + pipelineOutput: PipelineOutputType + accumulationFrames: number + dynamicAoEnabled: boolean + dynamicAoParams: DynamicAOPassParams + staticAoEnabled: boolean + staticAoParams: StaticAoPassParams } export const DefaultPipelineOptions: PipelineOptions = { - saoEnabled: true, - saoParams: { - saoBias: 0.15, - saoIntensity: 1.25, - saoScale: 434, - saoKernelRadius: 10, - saoMinResolution: 0, - saoBlur: true, - saoBlurRadius: 4, - saoBlurStdDev: 4, - saoBlurDepthCutoff: 0.0007 - }, - saoScaleOffset: 0, - saoNormalsRendering: NormalsType.ACCURATE + pipelineOutput: PipelineOutputType.FINAL, + accumulationFrames: 16, + dynamicAoEnabled: true, + dynamicAoParams: DefaultDynamicAOPassParams, + staticAoEnabled: true, + staticAoParams: DefaultStaticAoPassParams } export class Pipeline { + public static ACCUMULATE_FRAMES = 16 + private _renderer: WebGLRenderer = null private _batcher: Batcher = null - private _pipelineOptions: PipelineOptions = {} + private _pipelineOptions: PipelineOptions = Object.assign({}, DefaultPipelineOptions) + private _needsProgressive = false + private _resetFrame = false private composer: EffectComposer = null - private renderPass: RenderPass = null - private saoPass: SpeckleSAOPass = null - private applySaoPass: ApplySAOPass = null - private drawingSize: Vector2 = new Vector2() - public set pipelineOptions(options: PipelineOptions) { + private depthPass: DepthPass = null + private normalsPass: NormalsPass = null + private renderPass: ColorPass = null + private dynamicAoPass: DynamicSAOPass = null + private applySaoPass: ApplySAOPass = null + private copyOutputPass: CopyOutputPass = null + private staticAoPass: StaticAOPass = null + + private drawingSize: Vector2 = new Vector2() + private _renderType: RenderType = RenderType.NORMAL + private accumulationFrame = 0 + + public set pipelineOptions(options: Partial) { Object.assign(this._pipelineOptions, options) - if (this.saoPass) { - this.applySaoPass.enabled = this._pipelineOptions.saoEnabled - Object.assign(this.saoPass.params, this._pipelineOptions.saoParams) - this.saoPass.params.saoScale += this._pipelineOptions.saoScaleOffset - this.saoPass.normalsRendering = this._pipelineOptions.saoNormalsRendering + this.dynamicAoPass.setParams(options.dynamicAoParams) + this.staticAoPass.setParams(options.staticAoParams) + this.accumulationFrame = 0 + Pipeline.ACCUMULATE_FRAMES = options.accumulationFrames + + this.pipelineOutput = options.pipelineOutput + } + + public set pipelineOutput(outputType: PipelineOutputType) { + let pipeline = [] + this.clearPipeline() + switch (outputType) { + case PipelineOutputType.FINAL: + pipeline = this.getDefaultPipeline() + this.depthPass.depthSize = DepthSize.FULL + this.applySaoPass.setTexture('tDiffuse', this.staticAoPass.outputTexture) + this.applySaoPass.setTexture('tDiffuseInterp', this.dynamicAoPass.outputTexture) + this.needsProgressive = true + break + + case PipelineOutputType.DEPTH_RGBA: + pipeline.push(this.depthPass) + pipeline.push(this.copyOutputPass) + this.depthPass.depthSize = DepthSize.FULL + this.copyOutputPass.setTexture('tDiffuse', this.depthPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.DEPTH_RGBA) + this.needsProgressive = false + break + + case PipelineOutputType.DEPTH: + pipeline.push(this.depthPass) + pipeline.push(this.copyOutputPass) + this.depthPass.depthSize = DepthSize.FULL + this.copyOutputPass.setTexture('tDiffuse', this.depthPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.DEPTH) + this.needsProgressive = false + break + + case PipelineOutputType.COLOR: + pipeline.push(this.renderPass) + break + + case PipelineOutputType.GEOMETRY_NORMALS: + pipeline.push(this.normalsPass) + pipeline.push(this.copyOutputPass) + this.normalsPass.enabled = true + this.copyOutputPass.setTexture('tDiffuse', this.normalsPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.GEOMETRY_NORMALS) + this.needsProgressive = false + break + + case PipelineOutputType.RECONSTRUCTED_NORMALS: + pipeline.push(this.depthPass) + pipeline.push(this.dynamicAoPass) + pipeline.push(this.copyOutputPass) + this.dynamicAoPass.enabled = true + this.depthPass.depthType = DepthType.PERSPECTIVE_DEPTH + this.depthPass.depthSize = DepthSize.HALF + this.dynamicAoPass.setOutputType(DynamicAOOutputType.RECONSTRUCTED_NORMALS) + this.copyOutputPass.setTexture('tDiffuse', this.dynamicAoPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.GEOMETRY_NORMALS) + this.needsProgressive = false + break + + case PipelineOutputType.DYNAMIC_AO: + pipeline.push(this.depthPass) + pipeline.push(this.normalsPass) + pipeline.push(this.dynamicAoPass) + pipeline.push(this.copyOutputPass) + this.normalsPass.enabled = + this._pipelineOptions.dynamicAoParams.normalsType === NormalsType.DEFAULT + ? true + : false + this.dynamicAoPass.enabled = true + this.depthPass.depthType = DepthType.PERSPECTIVE_DEPTH + this.copyOutputPass.setTexture('tDiffuse', this.dynamicAoPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.COLOR) + this.dynamicAoPass.setOutputType(DynamicAOOutputType.AO) + this.needsProgressive = false + break + + case PipelineOutputType.DYNAMIC_AO_BLURED: + pipeline.push(this.depthPass) + pipeline.push(this.normalsPass) + pipeline.push(this.dynamicAoPass) + pipeline.push(this.copyOutputPass) + this.normalsPass.enabled = + this._pipelineOptions.dynamicAoParams.normalsType === NormalsType.DEFAULT + ? true + : false + this.dynamicAoPass.enabled = true + this.depthPass.depthType = DepthType.PERSPECTIVE_DEPTH + this.depthPass.depthSize = DepthSize.HALF + this.copyOutputPass.setTexture('tDiffuse', this.dynamicAoPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.COLOR) + this.dynamicAoPass.setOutputType(DynamicAOOutputType.AO_BLURRED) + this.needsProgressive = false + break + + case PipelineOutputType.PROGRESSIVE_AO: + pipeline.push(this.depthPass) + // pipeline.push(this.normalsPass) + pipeline.push(this.dynamicAoPass) + pipeline.push(this.staticAoPass) + pipeline.push(this.copyOutputPass) + this.depthPass.depthType = DepthType.LINEAR_DEPTH + this.depthPass.depthSize = DepthSize.FULL + this.copyOutputPass.setTexture('tDiffuse', this.staticAoPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.COLOR) + this.needsProgressive = true + break + default: + break } + this.setPipeline(pipeline) + } + + public set needsProgressive(value: boolean) { + this._needsProgressive = value + if (!value) this._renderType = RenderType.NORMAL + if (value && this._renderType === RenderType.NORMAL) + this._renderType = RenderType.ACCUMULATION + this.accumulationFrame = 0 + } + + public get renderType() { + return this._renderType } public constructor(renderer: WebGLRenderer, batcher: Batcher) { @@ -58,40 +220,169 @@ export class Pipeline { this.composer.writeBuffer = null } - public configure(scene: Scene, camera: Camera) { - this.saoPass = new SpeckleSAOPass( - scene, - camera, - this._batcher, - false, - NormalsType.IMPROVED - ) - this.composer.addPass(this.saoPass) - this.renderPass = new RenderPass(scene, camera) + public configure() { + this.depthPass = new DepthPass() + this.normalsPass = new NormalsPass() + this.dynamicAoPass = new DynamicSAOPass() + this.renderPass = new ColorPass() + this.applySaoPass = new ApplySAOPass() + this.staticAoPass = new StaticAOPass() + + this.copyOutputPass = new CopyOutputPass() + this.copyOutputPass.renderToScreen = true + + this.depthPass.setLayers([ObjectLayers.STREAM_CONTENT]) + this.normalsPass.setLayers([ObjectLayers.STREAM_CONTENT]) + this.renderPass.setLayers([ObjectLayers.PROPS, ObjectLayers.STREAM_CONTENT]) + + let restoreVisibility + this.depthPass.onBeforeRender = () => { + restoreVisibility = this._batcher.saveVisiblity() + const opaque = this._batcher.getOpaque() + this._batcher.applyVisibility(opaque) + } + this.depthPass.onAfterRender = () => { + this._batcher.applyVisibility(restoreVisibility) + } + + this.normalsPass.onBeforeRender = () => { + restoreVisibility = this._batcher.saveVisiblity() + const opaque = this._batcher.getOpaque() + this._batcher.applyVisibility(opaque) + } + this.normalsPass.onAfterRender = () => { + this._batcher.applyVisibility(restoreVisibility) + } + + this.setPipeline(this.getDefaultPipeline()) + } + + private getDefaultPipeline(): Array { this.renderPass.renderToScreen = true - this.composer.addPass(this.renderPass) - this.applySaoPass = new ApplySAOPass(this.saoPass.saoRenderTarget.texture) + this.normalsPass.enabled = + this._pipelineOptions.dynamicAoParams.normalsType === NormalsType.DEFAULT + ? true + : false + + this.dynamicAoPass.setOutputType( + this._pipelineOptions.dynamicAoParams.blurEnabled + ? DynamicAOOutputType.AO_BLURRED + : DynamicAOOutputType.AO + ) this.applySaoPass.renderToScreen = true - this.composer.addPass(this.applySaoPass) + + this.dynamicAoPass.setTexture('tDepth', this.depthPass.outputTextureHalf) + this.dynamicAoPass.setTexture('tNormal', this.normalsPass.outputTexture) + this.applySaoPass.setTexture('tDiffuse', this.dynamicAoPass.outputTexture) + this.applySaoPass.setTexture('tDiffuseInterp', this.dynamicAoPass.outputTexture) + this.staticAoPass.setTexture('tDepth', this.depthPass.outputTexture) + this.staticAoPass.setTexture('tNormal', this.normalsPass.outputTexture) + + const pipeline = [] + pipeline.push(this.depthPass) + pipeline.push(this.normalsPass) + pipeline.push(this.dynamicAoPass) + pipeline.push(this.staticAoPass) + pipeline.push(this.renderPass) + pipeline.push(this.applySaoPass) + + this.needsProgressive = true + return pipeline + } + + private clearPipeline() { + while (this.composer.passes.length > 0) { + this.composer.removePass(this.composer.passes[0]) + } + } + + private setPipeline(pipeline: Array) { + for (let k = 0; k < pipeline.length; k++) { + this.composer.addPass(pipeline[k] as unknown as Pass) + } } public updateClippingPlanes(planes: Plane[]) { - this.saoPass.depthMaterial.clippingPlanes = planes - this.saoPass.normalMaterial.clippingPlanes = planes + this.depthPass.setClippingPlanes(planes) } - public render(scene: Scene, camera: Camera) { + public reset() { + this._resetFrame = true + this.onStationaryEnd() + } + + public update(renderer: SpeckleRenderer) { + this.renderPass.update(renderer.scene, renderer.camera) + this.depthPass.update(renderer.scene, renderer.camera) + this.dynamicAoPass.update(renderer.scene, renderer.camera) + this.normalsPass.update(renderer.scene, renderer.camera) + this.staticAoPass.update(renderer.scene, renderer.camera) + this.applySaoPass.update(renderer.scene, renderer.camera) + + this.staticAoPass.setFrameIndex(this.accumulationFrame) + this.applySaoPass.setFrameIndex(this.accumulationFrame) + } + + public render(): boolean { this._renderer.getDrawingBufferSize(this.drawingSize) if (this.drawingSize.length() === 0) return - this.renderPass.scene = scene - this.renderPass.camera = camera - this.saoPass.scene = scene - this.saoPass.camera = camera - this.composer.render() + this._renderer.clear(true) + if (this._renderType === RenderType.NORMAL) { + this.composer.render() + const ret = false || this._resetFrame + if (this._resetFrame) { + this._resetFrame = false + this.onStationaryBegin() + } + return ret + } else { + // console.warn('Rendering accumulation frame -> ', this.accumulationFrame) + this.composer.render() + this.accumulationFrame++ + return this.accumulationFrame < Pipeline.ACCUMULATE_FRAMES + } } public resize(width: number, height: number) { this.composer.setSize(width, height) + this.accumulationFrame = 0 + } + + public onStationaryBegin() { + if (!this._needsProgressive) return + if (this._renderType === RenderType.ACCUMULATION) { + this.accumulationFrame = 0 + return + } + this._renderType = RenderType.ACCUMULATION + this.accumulationFrame = 0 + this.depthPass.enabled = true + this.depthPass.depthType = DepthType.LINEAR_DEPTH + this.depthPass.depthSize = DepthSize.FULL + this.normalsPass.enabled = false + this.dynamicAoPass.enabled = false + this.renderPass.enabled = true + this.applySaoPass.enabled = true + this.staticAoPass.enabled = true + this.applySaoPass.setTexture('tDiffuse', this.staticAoPass.outputTexture) + this.applySaoPass.setTexture('tDiffuseInterp', this.dynamicAoPass.outputTexture) + this.applySaoPass.setRenderType(this._renderType) + // console.warn('Starting stationary') + } + + public onStationaryEnd() { + if (!this._needsProgressive) return + if (this._renderType === RenderType.NORMAL) return + this.accumulationFrame = 0 + this._renderType = RenderType.NORMAL + this.depthPass.depthType = DepthType.PERSPECTIVE_DEPTH + this.depthPass.depthSize = DepthSize.HALF + this.staticAoPass.enabled = false + this.applySaoPass.enabled = true + this.dynamicAoPass.enabled = true + this.applySaoPass.setTexture('tDiffuse', this.dynamicAoPass.outputTexture) + this.applySaoPass.setRenderType(this._renderType) + // console.warn('Ending stationary') } } diff --git a/packages/viewer/src/modules/pipeline/SpecklePass.ts b/packages/viewer/src/modules/pipeline/SpecklePass.ts new file mode 100644 index 000000000..6d323bb7b --- /dev/null +++ b/packages/viewer/src/modules/pipeline/SpecklePass.ts @@ -0,0 +1,58 @@ +import { Camera, Plane, Scene, Texture } from 'three' +import { Pass } from 'three/examples/jsm/postprocessing/Pass' +import { ObjectLayers } from '../SpeckleRenderer' +import { RenderType } from './Pipeline' + +export type InputColorTextureUniform = 'tDiffuse' +export type InputDepthTextureUniform = 'tDepth' +export type InputNormalsTextureUniform = 'tNormal' +export type InputColorInterpolateTextureUniform = 'tDiffuseInterp' + +export interface SpecklePass { + onBeforeRender?: () => void + onAferRender?: () => void + + get displayName(): string + get outputTexture(): Texture + + update?(scene: Scene, camera: Camera) + setTexture?(uName: string, texture: Texture) + setParams?(params: unknown) + setClippingPlanes?(planes: Plane[]) + setLayers?(layers: ObjectLayers[]) +} + +export interface SpeckleProgressivePass extends SpecklePass { + setFrameIndex(index: number) + setRenderType?(type: RenderType) +} + +export abstract class BaseSpecklePass extends Pass implements SpecklePass { + protected layers: ObjectLayers[] = null + + constructor() { + super() + } + + get displayName(): string { + return 'BASE' + } + get outputTexture(): Texture { + return null + } + + public setLayers(layers: ObjectLayers[]) { + this.layers = layers + } + + protected applyLayers(camera: Camera) { + if (this.layers === null) { + camera.layers.enableAll() + return + } + camera.layers.disableAll() + this.layers.forEach((layer) => { + camera.layers.enable(layer) + }) + } +} diff --git a/packages/viewer/src/modules/pipeline/SpeckleSAOPass.ts b/packages/viewer/src/modules/pipeline/SpeckleSAOPass.ts deleted file mode 100644 index 94866ecc0..000000000 --- a/packages/viewer/src/modules/pipeline/SpeckleSAOPass.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { - Camera, - DoubleSide, - NoBlending, - OrthographicCamera, - PerspectiveCamera, - RGBADepthPacking, - Scene, - ShaderMaterial, - UniformsUtils, - Vector2 -} from 'three' -import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass' -import { SAOPass } from 'three/examples/jsm/postprocessing/SAOPass.js' -import { BlurShaderUtils } from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js' -import { speckleSaoFrag } from '../materials/shaders/speckle-sao-frag' -import { speckleSaoVert } from '../materials/shaders/speckle-sao-vert' -import { SAOShader } from 'three/examples/jsm/shaders/SAOShader.js' -import Batcher from '../batching/Batcher' -import SpeckleDepthMaterial from '../materials/SpeckleDepthMaterial' -import SpeckleNormalMaterial from '../materials/SpeckleNormalMaterial' - -export enum NormalsType { - DEFAULT = 0, - IMPROVED = 1, - ACCURATE = 2 -} - -/** - * SAO implementation inspired from bhouston previous SAO work - */ - -export class SpeckleSAOPass extends SAOPass { - private _oldClearColor - private prevStdDev - private prevNumSamples - private batcher: Batcher = null - private normalsType: NormalsType = NormalsType.IMPROVED - - public set normalsRendering(type: NormalsType) { - this.normalsType = type - this.saoMaterial.defines['NORMAL_TEXTURE'] = - this.normalsType === NormalsType.DEFAULT ? 1 : 0 - this.saoMaterial.defines['IMPROVED_NORMAL_RECONSTRUCTION'] = - this.normalsType === NormalsType.IMPROVED ? 1 : 0 - this.saoMaterial.defines['ACCURATE_NORMAL_RECONSTRUCTION'] = - this.normalsType === NormalsType.ACCURATE ? 1 : 0 - this.saoMaterial.needsUpdate = true - } - - constructor( - scene: Scene, - camera: Camera, - batcher: Batcher, - useDepthTexture = false, - normalsType: NormalsType, - resolution = new Vector2(256, 256) - ) { - super(scene, camera, useDepthTexture, true, resolution) - - this.batcher = batcher - - /** On Chromium, on MacOS the 16 bit depth render buffer appears broken. - * We're not really using a stencil buffer at all, we're just forcing - * three.js to use a 24 bit depth render buffer - */ - this.depthRenderTarget.depthBuffer = true - this.depthRenderTarget.stencilBuffer = true - this.normalRenderTarget.depthBuffer = true - this.normalRenderTarget.stencilBuffer = true - - this.depthMaterial = new SpeckleDepthMaterial( - { - depthPacking: RGBADepthPacking - }, - ['USE_RTE', 'ALPHATEST_REJECTION'] - ) - this.depthMaterial.blending = NoBlending - this.depthMaterial.side = DoubleSide - - this.normalMaterial = new SpeckleNormalMaterial({}, ['USE_RTE']) - this.normalMaterial.blending = NoBlending - this.normalMaterial.side = DoubleSide - - this.saoMaterial = new ShaderMaterial({ - defines: { - NUM_SAMPLES: 7, - NUM_RINGS: 4, - NORMAL_TEXTURE: 0, - DIFFUSE_TEXTURE: 0, - DEPTH_PACKING: 1, - PERSPECTIVE_CAMERA: 1 - }, - fragmentShader: speckleSaoFrag, - vertexShader: speckleSaoVert, - uniforms: UniformsUtils.clone(SAOShader.uniforms) - }) - this.normalsRendering = normalsType - this.saoMaterial.extensions.derivatives = true - this.saoMaterial.defines['DEPTH_PACKING'] = this.supportsDepthTextureExtension - ? 0 - : 1 - this.saoMaterial.defines['PERSPECTIVE_CAMERA'] = (this.camera as PerspectiveCamera) - .isPerspectiveCamera - ? 1 - : 0 - this.saoMaterial.uniforms['tDepth'].value = this.supportsDepthTextureExtension - ? this.beautyRenderTarget.depthTexture - : this.depthRenderTarget.texture - this.saoMaterial.uniforms['tNormal'].value = this.normalRenderTarget.texture - this.saoMaterial.uniforms['size'].value.set(this.resolution.x, this.resolution.y) - this.saoMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy( - this.camera.projectionMatrixInverse - ) - this.saoMaterial.uniforms['cameraProjectionMatrix'].value = - this.camera.projectionMatrix - this.saoMaterial.blending = NoBlending - } - - public render(renderer, writeBuffer, readBuffer) { - writeBuffer - readBuffer - if (this.params.output === 1) { - return - } - - renderer.getClearColor(this._oldClearColor) - this.oldClearAlpha = renderer.getClearAlpha() - renderer.autoClear = false - - renderer.setRenderTarget(this.depthRenderTarget) - renderer.clear() - - this.saoMaterial.uniforms['bias'].value = this.params.saoBias - this.saoMaterial.uniforms['intensity'].value = this.params.saoIntensity - this.saoMaterial.uniforms['scale'].value = this.params.saoScale - this.saoMaterial.uniforms['kernelRadius'].value = this.params.saoKernelRadius - this.saoMaterial.uniforms['minResolution'].value = this.params.saoMinResolution - this.saoMaterial.uniforms['cameraNear'].value = ( - this.camera as PerspectiveCamera | OrthographicCamera - ).near - this.saoMaterial.uniforms['cameraFar'].value = ( - this.camera as PerspectiveCamera | OrthographicCamera - ).far - this.saoMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy( - this.camera.projectionMatrixInverse - ) - this.saoMaterial.uniforms['cameraProjectionMatrix'].value = - this.camera.projectionMatrix - // this.saoMaterial.uniforms['randomSeed'].value = Math.random(); - - const depthCutoff = - this.params.saoBlurDepthCutoff * - ((this.camera as PerspectiveCamera | OrthographicCamera).far - - (this.camera as PerspectiveCamera | OrthographicCamera).near) - this.vBlurMaterial.uniforms['depthCutoff'].value = depthCutoff - this.hBlurMaterial.uniforms['depthCutoff'].value = depthCutoff - - this.vBlurMaterial.uniforms['cameraNear'].value = ( - this.camera as PerspectiveCamera | OrthographicCamera - ).near - this.vBlurMaterial.uniforms['cameraFar'].value = ( - this.camera as PerspectiveCamera | OrthographicCamera - ).far - this.hBlurMaterial.uniforms['cameraNear'].value = ( - this.camera as PerspectiveCamera | OrthographicCamera - ).near - this.hBlurMaterial.uniforms['cameraFar'].value = ( - this.camera as PerspectiveCamera | OrthographicCamera - ).far - - this.params.saoBlurRadius = Math.floor(this.params.saoBlurRadius) - if ( - this.prevStdDev !== this.params.saoBlurStdDev || - this.prevNumSamples !== this.params.saoBlurRadius - ) { - BlurShaderUtils.configure( - this.vBlurMaterial, - this.params.saoBlurRadius, - this.params.saoBlurStdDev, - new Vector2(0, 1) - ) - BlurShaderUtils.configure( - this.hBlurMaterial, - this.params.saoBlurRadius, - this.params.saoBlurStdDev, - new Vector2(1, 0) - ) - this.prevStdDev = this.params.saoBlurStdDev - this.prevNumSamples = this.params.saoBlurRadius - } - - const restoreVisibility = this.batcher.saveVisiblity() - const opaque = this.batcher.getOpaque() - this.batcher.applyVisibility(opaque) - // Re-render scene if depth texture extension is not supported - if (!this.supportsDepthTextureExtension) { - // Clear rule : far clipping plane in both RGBA and Basic encoding - this.renderOverride( - renderer, - this.depthMaterial, - this.depthRenderTarget, - 0x000000, - 1.0 - ) - } - - if (this.normalsType === NormalsType.DEFAULT) { - if (this.supportsNormalTexture) { - // Clear rule : default normal is facing the camera - this.renderOverride( - renderer, - this.normalMaterial, - this.normalRenderTarget, - 0x7777ff, - 1.0 - ) - } - } - this.batcher.applyVisibility(restoreVisibility) - - // Rendering SAO texture - this.renderPass(renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0) - - // Blurring SAO texture - if (this.params.saoBlur) { - this.renderPass( - renderer, - this.vBlurMaterial, - this.blurIntermediateRenderTarget, - 0xffffff, - 1.0 - ) - this.renderPass(renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0) - } - } - - public renderPass( - renderer, - passMaterial, - renderTarget, - clearColor = undefined, - clearAlpha = undefined - ) { - // save original state - renderer.getClearColor(this.originalClearColor) - const originalClearAlpha = renderer.getClearAlpha() - const originalAutoClear = renderer.autoClear - - renderer.setRenderTarget(renderTarget) - - // setup pass state - renderer.autoClear = false - if (clearColor !== undefined && clearColor !== null) { - renderer.setClearColor(clearColor) - renderer.setClearAlpha(clearAlpha || 0.0) - renderer.clear() - } - - ;(this.fsQuad as FullScreenQuad).material = passMaterial - ;(this.fsQuad as FullScreenQuad).render(renderer) - - // restore original state - renderer.autoClear = originalAutoClear - renderer.setClearColor(this.originalClearColor) - renderer.setClearAlpha(originalClearAlpha) - } - - public renderOverride( - renderer, - overrideMaterial, - renderTarget, - clearColor, - clearAlpha - ) { - renderer.getClearColor(this.originalClearColor) - const originalClearAlpha = renderer.getClearAlpha() - const originalAutoClear = renderer.autoClear - - renderer.setRenderTarget(renderTarget) - renderer.autoClear = false - - clearColor = overrideMaterial.clearColor || clearColor - clearAlpha = overrideMaterial.clearAlpha || clearAlpha - if (clearColor !== undefined && clearColor !== null) { - renderer.setClearColor(clearColor) - renderer.setClearAlpha(clearAlpha || 0.0) - renderer.clear() - } - - const shadowmapEnabled = renderer.shadowMap.enabled - const shadowmapNeedsUpdate = renderer.shadowMap.needsUpdate - this.scene.overrideMaterial = overrideMaterial - renderer.shadowMap.enabled = false - renderer.shadowMap.needsUpdate = false - renderer.render(this.scene, this.camera) - renderer.shadowMap.enabled = shadowmapEnabled - renderer.shadowMap.needsUpdate = shadowmapNeedsUpdate - this.scene.overrideMaterial = null - - // restore original state - renderer.autoClear = originalAutoClear - renderer.setClearColor(this.originalClearColor) - renderer.setClearAlpha(originalClearAlpha) - } -} diff --git a/packages/viewer/src/modules/pipeline/StaticAOPass.ts b/packages/viewer/src/modules/pipeline/StaticAOPass.ts new file mode 100644 index 000000000..c84717a81 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/StaticAOPass.ts @@ -0,0 +1,304 @@ +import { + AddEquation, + Camera, + Color, + CustomBlending, + DataTexture, + FloatType, + MathUtils, + Matrix4, + NoBlending, + OneFactor, + OrthographicCamera, + PerspectiveCamera, + RedFormat, + RepeatWrapping, + ReverseSubtractEquation, + Scene, + ShaderMaterial, + Texture, + Vector2, + Vector3, + WebGLRenderer, + WebGLRenderTarget +} from 'three' +import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass' + +import { speckleStaticAoGenerateVert } from '../materials/shaders/speckle-static-ao-generate-vert' +import { speckleStaticAoGenerateFrag } from '../materials/shaders/speckle-static-ao-generate-frag' +import { speckleStaticAoAccumulateVert } from '../materials/shaders/speckle-static-ao-accumulate-vert' +import { speckleStaticAoAccumulateFrag } from '../materials/shaders/speckle-static-ao-accumulate-frag' +import { SimplexNoise } from 'three/examples/jsm//math/SimplexNoise.js' +import { + InputDepthTextureUniform, + InputNormalsTextureUniform, + SpeckleProgressivePass +} from './SpecklePass' +import { Pipeline } from './Pipeline' +/** + * SAO implementation inspired from bhouston previous SAO work + */ + +export interface StaticAoPassParams { + intensity?: number + kernelRadius?: number + kernelSize?: number + bias?: number + minDistance?: number + maxDistance?: number +} + +export const DefaultStaticAoPassParams = { + intensity: 1, + kernelRadius: 30, // Screen space + kernelSize: 16, + bias: 0.01, + minDistance: 0, + maxDistance: 0.008 +} + +export class StaticAOPass extends Pass implements SpeckleProgressivePass { + public aoMaterial: ShaderMaterial = null + private accumulateMaterial: ShaderMaterial = null + private _generationBuffer: WebGLRenderTarget + private _accumulationBuffer: WebGLRenderTarget + private params: StaticAoPassParams = DefaultStaticAoPassParams + private fsQuad: FullScreenQuad + private frameIndex = 0 + private kernels: Array> = [] + private noiseTextures: Array = [] + + public setTexture( + uName: InputDepthTextureUniform | InputNormalsTextureUniform, + texture: Texture + ) { + if (uName === 'tDepth') { + this.aoMaterial.uniforms['tDepth'].value = texture + } + if (uName === 'tNormal') { + this.aoMaterial.uniforms['tNormal'].value = texture + } + this.aoMaterial.needsUpdate = true + } + + public get outputTexture() { + return this._accumulationBuffer.texture + } + + public get displayName(): string { + return 'STATIC-AO' + } + + constructor() { + super() + + this._generationBuffer = new WebGLRenderTarget(256, 256) + this._accumulationBuffer = new WebGLRenderTarget(256, 256) + + this.aoMaterial = new ShaderMaterial({ + fragmentShader: speckleStaticAoGenerateFrag, + vertexShader: speckleStaticAoGenerateVert, + uniforms: { + tDepth: { value: null }, + tNormal: { value: null }, + size: { value: new Vector2(512, 512) }, + + cameraNear: { value: 1 }, + cameraFar: { value: 100 }, + cameraProjectionMatrix: { value: new Matrix4() }, + cameraInverseProjectionMatrix: { value: new Matrix4() }, + tanFov: { value: 0 }, + + scale: { value: 1.0 }, + intensity: { value: 1 }, + bias: { value: 0 }, + + minResolution: { value: 0.0 }, + kernelRadius: { value: 0.5 }, // World space + + frameIndex: { value: 0 }, + + tNoise: { value: null }, + kernel: { value: null }, + minDistance: { value: 0.001 }, + maxDistance: { value: 1 } + } + }) + + this.aoMaterial.extensions.derivatives = true + this.aoMaterial.uniforms['size'].value.set(256, 256) + this.aoMaterial.blending = NoBlending + + this.accumulateMaterial = new ShaderMaterial({ + defines: {}, + fragmentShader: speckleStaticAoAccumulateFrag, + vertexShader: speckleStaticAoAccumulateVert, + uniforms: { + tDiffuse: { value: null }, + opacity: { value: 1 } + } + }) + this.accumulateMaterial.uniforms['tDiffuse'].value = this._generationBuffer.texture + this.accumulateMaterial.blending = CustomBlending + this.accumulateMaterial.blendSrc = OneFactor + this.accumulateMaterial.blendDst = OneFactor + this.accumulateMaterial.blendEquation = ReverseSubtractEquation + this.accumulateMaterial.blendSrcAlpha = OneFactor + this.accumulateMaterial.blendDstAlpha = OneFactor + this.accumulateMaterial.blendEquationAlpha = AddEquation + + this.fsQuad = new FullScreenQuad(this.aoMaterial) + } + + public setParams(params: unknown) { + Object.assign(this.params, params) + this.kernels = [] + this.noiseTextures = [] + } + + public setFrameIndex(index: number) { + this.frameIndex = index + } + + public update(scene: Scene, camera: Camera) { + /** DEFINES */ + this.aoMaterial.defines['PERSPECTIVE_CAMERA'] = (camera as PerspectiveCamera) + .isPerspectiveCamera + ? 1 + : 0 + this.aoMaterial.defines['NUM_FRAMES'] = Pipeline.ACCUMULATE_FRAMES + this.aoMaterial.defines['KERNEL_SIZE'] = this.params.kernelSize + this.accumulateMaterial.defines['NUM_FRAMES'] = Pipeline.ACCUMULATE_FRAMES + /** UNIFORMS */ + this.aoMaterial.uniforms['cameraNear'].value = ( + camera as PerspectiveCamera | OrthographicCamera + ).near + this.aoMaterial.uniforms['cameraFar'].value = ( + camera as PerspectiveCamera | OrthographicCamera + ).far + this.aoMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy( + camera.projectionMatrixInverse + ) + this.aoMaterial.uniforms['cameraProjectionMatrix'].value.copy( + camera.projectionMatrix + ) + const fov = (((camera as PerspectiveCamera).fov / 2) * Math.PI) / 180.0 + this.aoMaterial.uniforms['tanFov'].value = Math.tan(fov) + + if (!this.kernels[this.frameIndex]) { + this.generateSampleKernel(this.frameIndex) + } + if (!this.noiseTextures[this.frameIndex]) { + this.generateRandomKernelRotations(this.frameIndex) + } + this.aoMaterial.uniforms['kernel'].value = this.kernels[this.frameIndex] + this.aoMaterial.uniforms['tNoise'].value = this.noiseTextures[this.frameIndex] + + this.aoMaterial.uniforms['intensity'].value = this.params.intensity + this.aoMaterial.uniforms['kernelRadius'].value = this.params.kernelRadius + this.aoMaterial.uniforms['bias'].value = this.params.bias + this.aoMaterial.uniforms['frameIndex'].value = this.frameIndex + + this.aoMaterial.uniforms['minDistance'].value = this.params.minDistance + this.aoMaterial.uniforms['maxDistance'].value = this.params.maxDistance + this.aoMaterial.needsUpdate = true + this.accumulateMaterial.needsUpdate = true + } + + public render(renderer, writeBuffer, readBuffer) { + writeBuffer + readBuffer + // save original state + const originalClearColor = new Color() + renderer.getClearColor(originalClearColor) + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear + + this.renderFrame(renderer) + // restore original state + renderer.autoClear = originalAutoClear + renderer.setClearColor(originalClearColor) + renderer.setClearAlpha(originalClearAlpha) + } + + private renderFrame(renderer: WebGLRenderer) { + renderer.setRenderTarget(this._generationBuffer) + renderer.autoClear = false + renderer.setClearColor(0x000000) + renderer.setClearAlpha(1) + renderer.clear(true) + this.fsQuad.material = this.aoMaterial + this.fsQuad.render(renderer) + + renderer.setRenderTarget(this._accumulationBuffer) + if (this.frameIndex === 0) { + renderer.setClearColor(0xffffff) + renderer.setClearAlpha(1) + renderer.clear(true) + } + + this.fsQuad.material = this.accumulateMaterial + this.fsQuad.render(renderer) + } + + public setSize(width: number, height: number) { + this._generationBuffer.setSize(width, height) + this._accumulationBuffer.setSize(width, height) + + this.aoMaterial.uniforms['size'].value.set(width, height) + this.aoMaterial.needsUpdate = true + } + + private generateSampleKernel(frameIndex: number) { + const kernelSize = this.params.kernelSize + this.kernels[frameIndex] = [] + + for (let i = 0; i < kernelSize; i++) { + const sample = new Vector3() + sample.x = Math.random() * 2 - 1 + sample.y = Math.random() * 2 - 1 + sample.z = Math.random() + + sample.normalize() + + let scale = i / kernelSize + scale = MathUtils.lerp(0.1, 1, scale * scale) + sample.multiplyScalar(scale) + + this.kernels[frameIndex].push(sample) + } + } + + private generateRandomKernelRotations(frameIndex: number) { + const width = 4, + height = 4 + + if (SimplexNoise === undefined) { + console.error('THREE.SSAOPass: The pass relies on SimplexNoise.') + } + + const simplex = new SimplexNoise() + + const size = width * height + const data = new Float32Array(size) + + for (let i = 0; i < size; i++) { + const x = Math.random() * 2 - 1 + const y = Math.random() * 2 - 1 + const z = 0 + + data[i] = simplex.noise3d(x, y, z) + } + + this.noiseTextures[frameIndex] = new DataTexture( + data, + width, + height, + RedFormat, + FloatType + ) + this.noiseTextures[frameIndex].wrapS = RepeatWrapping + this.noiseTextures[frameIndex].wrapT = RepeatWrapping + this.noiseTextures[frameIndex].needsUpdate = true + } +} From 0c99573bc68dfc377c8fc99ba5433410f54e0ed0 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:01:19 +0000 Subject: [PATCH 06/10] Fixes liveness and readiness checks to prevent CSRF error message (#1169) - provides content-type header - check that status code is 200 --- utils/helm/speckle-server/templates/server/deployment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml index ab3e3defd..510957713 100644 --- a/utils/helm/speckle-server/templates/server/deployment.yml +++ b/utils/helm/speckle-server/templates/server/deployment.yml @@ -70,7 +70,7 @@ spec: command: - node - -e - - require('request')('http://localhost:3000/graphql?query={serverInfo{version}}', (e,r,b) => process.exit(b.toLowerCase().includes('error'))) + - require('request')({headers: {'Content-Type': 'application/json'}, uri: 'http://localhost:3000/graphql?query={serverInfo{version}}', method: 'GET' }, (e,r,b) => process.exit(r.statusCode != 200 || b.toLowerCase().includes('error'))) readinessProbe: initialDelaySeconds: 5 @@ -80,7 +80,7 @@ spec: command: - node - -e - - require('request')('http://localhost:3000/graphql?query={serverInfo{version}}', (e,r,b) => process.exit(b.toLowerCase().includes('error'))) + - require('request')({headers: {'Content-Type': 'application/json'}, uri: 'http://localhost:3000/graphql?query={serverInfo{version}}', method: 'GET' }, (e,r,b) => process.exit(r.statusCode != 200 || b.toLowerCase().includes('error'))) env: - name: CANONICAL_URL From df250d616da1777ac64c1c57199debfd5cca1258 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:40:03 +0000 Subject: [PATCH 07/10] Fixes broken helm template by adding quotation marks around liveness probe command (#1171) --- utils/helm/speckle-server/templates/server/deployment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml index 510957713..f34f5e465 100644 --- a/utils/helm/speckle-server/templates/server/deployment.yml +++ b/utils/helm/speckle-server/templates/server/deployment.yml @@ -70,7 +70,7 @@ spec: command: - node - -e - - require('request')({headers: {'Content-Type': 'application/json'}, uri: 'http://localhost:3000/graphql?query={serverInfo{version}}', method: 'GET' }, (e,r,b) => process.exit(r.statusCode != 200 || b.toLowerCase().includes('error'))) + - "require('request')({headers: {'Content-Type': 'application/json'}, uri: 'http://localhost:3000/graphql?query={serverInfo{version}}', method: 'GET' }, (e,r,b) => process.exit(r.statusCode != 200 || b.toLowerCase().includes('error')))" readinessProbe: initialDelaySeconds: 5 @@ -80,7 +80,7 @@ spec: command: - node - -e - - require('request')({headers: {'Content-Type': 'application/json'}, uri: 'http://localhost:3000/graphql?query={serverInfo{version}}', method: 'GET' }, (e,r,b) => process.exit(r.statusCode != 200 || b.toLowerCase().includes('error'))) + - "require('request')({headers: {'Content-Type': 'application/json'}, uri: 'http://localhost:3000/graphql?query={serverInfo{version}}', method: 'GET' }, (e,r,b) => process.exit(r.statusCode != 200 || b.toLowerCase().includes('error')))" env: - name: CANONICAL_URL From fc2534e9b47c4e277c34e7155c34786682c51bc6 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:45:15 +0000 Subject: [PATCH 08/10] Always build, except when branch (not main) does not have PR (#1167) * Always build, except when branch (not main) does not have PR * Logic fix * Should fail the step * Debug * More circleci debugging * More and more circleci debugging * More more more debugging * Attempt to explicitly exit * :facepalm: the script required a passing line in order to exit non-zero --- .circleci/config.yml | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f9c3efed..0b535cb0b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,14 +14,6 @@ workflows: - get-version: filters: *filters-allow-all - - build-approval: - type: approval - filters: &filters-ignore-main-branch-all-tags - branches: - ignore: main - tags: - ignore: /.*/ - - pre-commit: filters: *filters-allow-all @@ -32,51 +24,48 @@ workflows: requires: - test-server - get-version - - build-approval - docker-build-frontend: filters: *filters-build requires: - get-version - - build-approval - docker-build-webhooks: filters: *filters-build requires: - get-version - test-server - - build-approval - docker-build-file-imports: filters: *filters-build requires: - get-version - test-server - - build-approval - docker-build-previews: filters: *filters-build requires: - get-version - test-server - - build-approval - docker-build-test-container: filters: *filters-build requires: - get-version - test-server - - build-approval - docker-build-monitor-container: filters: *filters-build requires: - get-version - - build-approval - publish-approval: type: approval - filters: *filters-ignore-main-branch-all-tags + filters: &filters-ignore-main-branch-or-all-tags + branches: + ignore: main + tags: + ignore: /.*/ - docker-publish-server: context: &docker-hub-context @@ -336,6 +325,11 @@ jobs: - attach_workspace: at: /tmp/ci/workspace - run: cat workspace/env-vars >> $BASH_ENV + - run: + name: 'Check if should proceed' + command: | + [[ "${CIRCLE_BRANCH}" != "main" && -z "${CIRCLE_PULL_REQUEST}" ]] && echo "Should not build, stopping" && circleci-agent step halt && exit 1 + echo "proceeding" - setup_remote_docker: # a weird issue with yarn installing packages throwing EPERM errors # this fixes it From c06650c47545e91c002e97bc8fa287e3d655047e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= <57442769+gjedlicska@users.noreply.github.com> Date: Wed, 2 Nov 2022 12:57:46 +0100 Subject: [PATCH 09/10] Update to new specklepy (#1173) * Publish images for all branches but limit tagging * only tag 'latest' and '2' when 'SHOULD_PUBLISH' variable is 'true' * Publishing helm chart should check for `SHOULD_PUBLISH` * Move blocking step to publish-helm chart, and allow images to be published * Pin python requirements and bump to latest versions * Fix EOL whitespace * use valid version for psycopg2-binary (the clue is in the 2!) * fix(fileimports): add exception printing to file imports * fix(fileimports): bump specklepy version move to a specklepy version that contains a fix for send without writing to disk Co-authored-by: Iain Sproat <68657+iainsproat@users.noreply.github.com> --- .../fileimport-service/obj/import_file.py | 102 ++++++++++-------- packages/fileimport-service/requirements.txt | 2 +- .../fileimport-service/stl/import_file.py | 52 +++++---- 3 files changed, 89 insertions(+), 67 deletions(-) diff --git a/packages/fileimport-service/obj/import_file.py b/packages/fileimport-service/obj/import_file.py index 853440538..a63aa4f80 100644 --- a/packages/fileimport-service/obj/import_file.py +++ b/packages/fileimport-service/obj/import_file.py @@ -1,53 +1,58 @@ - +import sys, os import json from specklepy.objects import Base from specklepy.objects.other import RenderMaterial -from specklepy.objects.geometry import Mesh, Point, Line +from specklepy.objects.geometry import Mesh from specklepy.transports.server import ServerTransport from specklepy.api.client import SpeckleClient -from specklepy.api.credentials import get_default_account from specklepy.api import operations - -import sys, os - from obj_file import ObjFile -TMP_RESULTS_PATH = '/tmp/import_result.json' -DEFAULT_BRANCH = 'uploads' + +TMP_RESULTS_PATH = "/tmp/import_result.json" +DEFAULT_BRANCH = "uploads" + def convert_material(obj_mat): speckle_mat = RenderMaterial() - speckle_mat.name = obj_mat['name'] - if 'diffuse' in obj_mat: - argb = [1,] + obj_mat['diffuse'] - speckle_mat.diffuse = int.from_bytes([int(val * 255) for val in argb], byteorder="big", signed=True) - if 'dissolved' in obj_mat: - speckle_mat.opacity = obj_mat['dissolved'] + speckle_mat.name = obj_mat["name"] + if "diffuse" in obj_mat: + argb = [ + 1, + ] + obj_mat["diffuse"] + speckle_mat.diffuse = int.from_bytes( + [int(val * 255) for val in argb], byteorder="big", signed=True + ) + if "dissolved" in obj_mat: + speckle_mat.opacity = obj_mat["dissolved"] return speckle_mat + def import_obj(): - file_path, user_id, stream_id, branch_name, commit_message = sys.argv[1:] - print(f'ImportOBJ argv[1:]: {sys.argv[1:]}') + file_path, _, stream_id, branch_name, commit_message = sys.argv[1:] + print(f"ImportOBJ argv[1:]: {sys.argv[1:]}") # Parse input obj = ObjFile(file_path) - print(f'Parsed obj with {len(obj.faces)} faces ({len(obj.vertices) * 3} vertices)') + print(f"Parsed obj with {len(obj.faces)} faces ({len(obj.vertices) * 3} vertices)") speckle_root = Base() - speckle_root['@objects'] = [] + speckle_root["@objects"] = [] for objname in obj.objects: - print(f' Converting {objname}...') + print(f" Converting {objname}...") speckle_obj = Base() speckle_obj.name = objname - speckle_obj['@displayValue'] = [] - speckle_root['@objects'].append(speckle_obj) - + speckle_obj["@displayValue"] = [] + speckle_root["@objects"].append(speckle_obj) + for obj_mesh in obj.objects[objname]: - speckle_vertices = [coord for point in obj_mesh['vertices'] for coord in point] + speckle_vertices = [ + coord for point in obj_mesh["vertices"] for coord in point + ] speckle_faces = [] - for obj_face in obj_mesh['faces']: + for obj_face in obj_mesh["faces"]: if len(obj_face) == 3: speckle_faces.append(0) elif len(obj_face) == 4: @@ -57,64 +62,75 @@ def import_obj(): speckle_faces.extend(obj_face) has_vertex_colors = False - for vc in obj_mesh['vertex_colors']: + for vc in obj_mesh["vertex_colors"]: if vc is not None: has_vertex_colors = True colors = [] if has_vertex_colors: - for vc in obj_mesh['vertex_colors']: + for vc in obj_mesh["vertex_colors"]: if vc is None: r, g, b = (1.0, 1.0, 1.0) else: r, g, b = vc argb = (1.0, r, g, b) - color = int.from_bytes([int(val * 255) for val in argb], byteorder="big", signed=True) + color = int.from_bytes( + [int(val * 255) for val in argb], byteorder="big", signed=True + ) colors.append(color) speckle_mesh = Mesh( vertices=speckle_vertices, faces=speckle_faces, colors=colors, - textureCoordinates=[] + textureCoordinates=[], ) - obj_material = obj_mesh['material'] + obj_material = obj_mesh["material"] if obj_material: - speckle_mesh['renderMaterial'] = convert_material(obj_material) + speckle_mesh["renderMaterial"] = convert_material(obj_material) - speckle_obj['@displayValue'].append(speckle_mesh) + speckle_obj["@displayValue"].append(speckle_mesh) # Commit - client = SpeckleClient(host=os.getenv('SPECKLE_SERVER_URL', 'localhost:3000'), use_ssl=False) - client.authenticate(os.environ['USER_TOKEN']) + client = SpeckleClient( + host=os.getenv("SPECKLE_SERVER_URL", "localhost:3000"), use_ssl=False + ) + client.authenticate_with_token(os.environ["USER_TOKEN"]) if not client.branch.get(stream_id, branch_name): - client.branch.create(stream_id, branch_name, 'File upload branch' if branch_name == 'uploads' else '') + client.branch.create( + stream_id, + branch_name, + "File upload branch" if branch_name == "uploads" else "", + ) transport = ServerTransport(client=client, stream_id=stream_id) - id = operations.send(base=speckle_root, transports=[transport], use_default_cache=False) + id = operations.send( + base=speckle_root, transports=[transport], use_default_cache=False + ) commit_id = client.commit.create( stream_id=stream_id, object_id=id, branch_name=(branch_name or DEFAULT_BRANCH), - message=(commit_message or 'OBJ file upload'), - source_application='OBJ' + message=(commit_message or "OBJ file upload"), + source_application="OBJ", ) return commit_id -if __name__ == '__main__': +if __name__ == "__main__": + from pathlib import Path + try: commit_id = import_obj() if not commit_id: raise Exception("Can't create commit") - results = {'success': True, 'commitId': commit_id} + results = {"success": True, "commitId": commit_id} except Exception as ex: - print('ERROR: ' + str(ex)) - results = {'success': False, 'error': str(ex)} + print("ERROR: " + str(ex)) + results = {"success": False, "error": str(ex)} - with open(TMP_RESULTS_PATH, 'w') as f: - json.dump(results, f) + Path(TMP_RESULTS_PATH).write_text(json.dumps(results)) diff --git a/packages/fileimport-service/requirements.txt b/packages/fileimport-service/requirements.txt index 7b3349251..f7e1614c8 100644 --- a/packages/fileimport-service/requirements.txt +++ b/packages/fileimport-service/requirements.txt @@ -1,2 +1,2 @@ numpy-stl==2.17.1 -specklepy==2.9.0 +specklepy==2.9.1 diff --git a/packages/fileimport-service/stl/import_file.py b/packages/fileimport-service/stl/import_file.py index 8de5c4f95..ac7d8740c 100644 --- a/packages/fileimport-service/stl/import_file.py +++ b/packages/fileimport-service/stl/import_file.py @@ -1,46 +1,50 @@ - import json import stl -from specklepy.objects.geometry import Mesh, Point +from specklepy.objects.geometry import Mesh from specklepy.transports.server import ServerTransport from specklepy.api.client import SpeckleClient -from specklepy.api.credentials import get_default_account from specklepy.api import operations import sys, os -TMP_RESULTS_PATH = '/tmp/import_result.json' -DEFAULT_BRANCH = 'uploads' +TMP_RESULTS_PATH = "/tmp/import_result.json" +DEFAULT_BRANCH = "uploads" + def import_stl(): - file_path, user_id, stream_id, branch_name, commit_message = sys.argv[1:] - print(f'ImportSTL argv[1:]: {sys.argv[1:]}') + file_path, _, stream_id, branch_name, commit_message = sys.argv[1:] + print(f"ImportSTL argv[1:]: {sys.argv[1:]}") # Parse input stl_mesh = stl.mesh.Mesh.from_file(file_path) - print(f'Parsed mesh with {stl_mesh.points.shape[0]} faces ({stl_mesh.points.shape[0] * 3} vertices)') + print( + f"Parsed mesh with {stl_mesh.points.shape[0]} faces ({stl_mesh.points.shape[0] * 3} vertices)" + ) # Construct speckle obj vertices = stl_mesh.points.flatten().tolist() faces = [] for i in range(stl_mesh.points.shape[0]): - faces.extend([0, 3*i, 3*i+1, 3*i+2]) + faces.extend([0, 3 * i, 3 * i + 1, 3 * i + 2]) speckle_mesh = Mesh( - vertices=vertices, - faces=faces, - colors=[], - textureCoordinates=[] + vertices=vertices, faces=faces, colors=[], textureCoordinates=[] ) - print('Constructed Speckle Mesh object') + print("Constructed Speckle Mesh object") # Commit - client = SpeckleClient(host=os.getenv('SPECKLE_SERVER_URL', 'localhost:3000'), use_ssl=False) - client.authenticate(os.environ['USER_TOKEN']) + client = SpeckleClient( + host=os.getenv("SPECKLE_SERVER_URL", "localhost:3000"), use_ssl=False + ) + client.authenticate_with_token(os.environ["USER_TOKEN"]) if not client.branch.get(stream_id, branch_name): - client.branch.create(stream_id, branch_name, 'File upload branch' if branch_name == 'uploads' else '') + client.branch.create( + stream_id, + branch_name, + "File upload branch" if branch_name == "uploads" else "", + ) transport = ServerTransport(client=client, stream_id=stream_id) id = operations.send( @@ -53,20 +57,22 @@ def import_stl(): stream_id=stream_id, object_id=id, branch_name=(branch_name or DEFAULT_BRANCH), - message=(commit_message or 'STL file upload'), - source_application='STL' + message=(commit_message or "STL file upload"), + source_application="STL", ) return commit_id -if __name__ == '__main__': +if __name__ == "__main__": + from pathlib import Path + try: commit_id = import_stl() - results = {'success': True, 'commitId': commit_id} + results = {"success": True, "commitId": commit_id} except Exception as ex: results = {'success': False, 'error': str(ex)} print(ex) - with open(TMP_RESULTS_PATH, 'w') as f: - json.dump(results, f) + print(results) + Path(TMP_RESULTS_PATH).write_text(json.dumps(results)) From de9beccd229f5ad651bfdad7cb332ccf86568bdb Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:16:53 +0000 Subject: [PATCH 10/10] Helm test is deployed as a job (#1174) - this allows it to be identified in alerting more easily --- .../templates/tests/deployment.yml | 78 ++++++++++--------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/utils/helm/speckle-server/templates/tests/deployment.yml b/utils/helm/speckle-server/templates/tests/deployment.yml index e29205717..25b977c26 100644 --- a/utils/helm/speckle-server/templates/tests/deployment.yml +++ b/utils/helm/speckle-server/templates/tests/deployment.yml @@ -1,49 +1,53 @@ {{- if .Values.helm_test_enabled }} -apiVersion: v1 -kind: Pod + +apiVersion: batch/v1 +kind: Job metadata: - name: "speckle-test-deployment" + name: "speckle-test" namespace: {{ .Values.namespace }} annotations: - "helm.sh/hook": test + helm.sh/hook: test labels: {{ include "test.labels" . | indent 4 }} spec: - containers: - - name: test-deployment - image: speckle/speckle-test-deployment:{{ .Values.docker_image_tag }} - env: - - name: SPECKLE_SERVER - value: https://{{ .Values.domain }} - - name: SERVER_VERSION - value: {{ .Values.docker_image_tag }} - resources: - requests: - cpu: {{ .Values.test.requests.cpu }} - memory: {{ .Values.test.requests.memory }} - limits: - cpu: {{ .Values.test.limits.cpu }} - memory: {{ .Values.test.limits.memory }} + backoffLimit: 1 + parallelism: 1 + completions: 1 + template: + spec: + containers: + - name: test-deployment + image: speckle/speckle-test-deployment:{{ .Values.docker_image_tag }} + env: + - name: SPECKLE_SERVER + value: https://{{ .Values.domain }} + - name: SERVER_VERSION + value: {{ .Values.docker_image_tag }} + resources: + requests: + cpu: {{ .Values.test.requests.cpu }} + memory: {{ .Values.test.requests.memory }} + limits: + cpu: {{ .Values.test.limits.cpu }} + memory: {{ .Values.test.limits.memory }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + restartPolicy: Never securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 20000 + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault - restartPolicy: Never - - securityContext: - runAsNonRoot: true - runAsUser: 20000 - runAsGroup: 30000 - seccompProfile: - type: RuntimeDefault - - {{- if .Values.test.serviceAccount.create }} - serviceAccountName: {{ include "test.name" $ }} - {{- end }} + {{- if .Values.test.serviceAccount.create }} + serviceAccountName: {{ include "test.name" $ }} + {{- end }} {{- end }}